@behindthescenes/analytics
v0.0.12
Published
Browser analytics SDK for BTS external-site event tracking and attribution.
Downloads
1,464
Readme
@behindthescenes/analytics
Browser SDK for BTS external-site analytics. Track visitor behavior, capture conversion events, and maintain attribution across the customer journey.
Overview
The BTS Analytics SDK provides:
- Automatic event tracking - Page views, clicks, form submissions, and content views captured without code changes
- Conversion tracking - Standard e-commerce and lead events (purchase, checkout, sign_up, lead)
- Cross-site attribution - Journey tokens preserve visitor context across domains
- Attribution preservation - UTM parameters and click IDs captured and persisted
- GoHighLevel integration - Contact form submissions directly to GHL
- Google Analytics compatibility - Detects existing GA installations or loads tags in proxy mode
Installation
npm/yarn/pnpm/bun
npm install @behindthescenes/analyticspnpm add @behindthescenes/analyticsyarn add @behindthescenes/analyticsbun add @behindthescenes/analyticsBrowser Bundle (Hosted)
<script type="module">
import { createBTSAnalytics } from "https://behindthescenes.com/sdk/analytics/latest/browser/browser.js";
window.btsAnalytics = createBTSAnalytics({
siteKey: "your-public-site-key"
});
</script>Quick Start
import { createBTSAnalytics } from "@behindthescenes/analytics";
const analytics = createBTSAnalytics({
siteKey: "your-public-site-key",
});Setup in BTS
Before using the SDK, configure your site in the BTS platform:
1. Get Your Site Key
- In BTS, navigate to your creator dashboard
- Go to Settings > Website Integration
- Copy your Public Site Key
2. Configure GoHighLevel (Optional)
For contact form submissions:
- Go to Settings > Integrations > GoHighLevel
- Connect your GHL account
- Note your Location ID for use with
submitContactForm()
3. Enable External Site Tracking
Ensure your domain is added to the allowed domains list in your BTS space settings.
Framework Setup
React
// BTSAnalyticsProvider.tsx
import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
import { createContext, PropsWithChildren, useContext, useMemo } from "react";
const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);
export function BTSAnalyticsProvider({ children }: PropsWithChildren) {
const analytics = useMemo(() =>
createBTSAnalytics({
siteKey: "your-public-site-key",
autoPageviews: true,
}),
[]
);
return (
<BTSAnalyticsContext.Provider value={analytics}>
{children}
</BTSAnalyticsContext.Provider>
);
}
export function useBTSAnalytics() {
const analytics = useContext(BTSAnalyticsContext);
if (!analytics) throw new Error("useBTSAnalytics must be used inside BTSAnalyticsProvider");
return analytics;
}Usage:
const analytics = useBTSAnalytics();
analytics.track("cta_clicked", { placement: "hero" });Next.js
// app/providers.tsx
"use client";
import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
import { createContext, PropsWithChildren, useContext, useMemo } from "react";
const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);
export function Providers({ children }: PropsWithChildren) {
const analytics = useMemo(() =>
createBTSAnalytics({
siteKey: "your-public-site-key",
autoPageviews: true,
}),
[]
);
return (
<BTSAnalyticsContext.Provider value={analytics}>
{children}
</BTSAnalyticsContext.Provider>
);
}
export function useBTSAnalytics() {
const analytics = useContext(BTSAnalyticsContext);
if (!analytics) throw new Error("useBTSAnalytics must be used inside Providers");
return analytics;
}Remix
// app/root.tsx
import { createBTSAnalytics } from "@behindthescenes/analytics";
import { useEffect, useMemo } from "react";
import { Outlet, useLocation } from "react-router";
export const analytics = createBTSAnalytics({
siteKey: "your-public-site-key",
autoPageviews: false,
});
export function Layout() {
const location = useLocation();
const path = useMemo(() => location.pathname + location.search, [location]);
useEffect(() => {
analytics.page(path);
}, [path]);
return <Outlet />;
}Vue
// src/plugins/bts-analytics.ts
import { createBTSAnalytics } from "@behindthescenes/analytics";
import type { App } from "vue";
export const analytics = createBTSAnalytics({
siteKey: "your-public-site-key",
autoPageviews: true,
});
export function installBTSAnalytics(app: App) {
app.provide("btsAnalytics", analytics);
}<script setup lang="ts">
import { inject } from "vue";
import type { BTSAnalytics } from "@behindthescenes/analytics";
const analytics = inject<BTSAnalytics>("btsAnalytics")!;
function trackEvent() {
analytics.track("button_clicked", { buttonId: "cta" });
}
</script>Plain JavaScript
<script async src="https://api.bts.it.com/v2/website/analytics/sdk/analytics/latest/browser/browser.js"></script>
<script>
window.btsDataLayer = window.btsDataLayer || [];
function bts(){window.btsDataLayer.push(arguments);}
bts("js", new Date());
bts("config", "your-public-site-key", {
autoPageviews: true
});
</script>See the docs folder for complete framework guides:
- Frameworks Guide - React, Next.js, Remix, Vue, Svelte, Angular, SolidJS
- Advanced Setup - Custom endpoints, request signing, GA integration, SPA config
- API Reference - Complete API documentation
Auto-Tracked Events
The SDK automatically captures these events without additional code:
| Event | Trigger | Description |
|-------|---------|-------------|
| page_view | Page load, SPA navigation | Initial load and history changes |
| outbound_click | External link clicks | Links to different origins |
| button_click | Button interactions | button, [role="button"], or [data-bts-track-click] |
| form_submit | Form submissions | Any form submit event |
| search | GET search forms | Forms with query params q, query, or search |
| view_content | Element visibility | Elements with data-bts-view-content |
Content View Tracking
Opt-in to automatic content-view tracking by marking elements:
<article
data-bts-view-content="offer_123"
data-bts-content-type="offer"
data-bts-content-title="Creator Accelerator"
>
Creator Accelerator
</article>Standard Events
Use trackStandard() for built-in conversion and engagement events:
// Engagement
analytics.trackStandard("page_view");
analytics.trackStandard("view_content", { contentId: "offer-42" });
analytics.trackStandard("search", { query: "pricing" });
// Lead & Account
analytics.trackStandard("lead", { formId: "newsletter" });
analytics.trackStandard("sign_up", { method: "email" });
// E-commerce
analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
analytics.trackStandard("add_payment_info", { checkoutId: "offer_123" });
analytics.trackStandard("purchase", {
orderId: "order_123",
monetaryValue: 149,
currency: "USD"
});
// Engagement
analytics.trackStandard("outbound_click", { url: "https://partner.com" });
analytics.trackStandard("button_click", { buttonId: "cta-primary" });
analytics.trackStandard("form_submit", { formId: "contact" });Legacy Event Aliases
These aliases are automatically normalized to standard events:
| Legacy Name | Standard Event |
|-------------|----------------|
| $pageview | page_view |
| checkout_started | begin_checkout |
| lead_capture | lead |
| purchase_completed | purchase |
| registration_complete | sign_up |
Custom Events
Track arbitrary events with custom properties:
analytics.track("video_played", {
videoId: "intro-01",
duration: 120,
autoplay: false
});
analytics.track("cta_clicked", {
ctaId: "hero-primary",
placement: "hero",
variant: "blue"
});User Identification
Associate events with a known user:
analytics.identify("user_123", {
email: "[email protected]",
name: "John Doe",
plan: "pro"
});Contact Form Submission
Submit contact enquiries directly to GoHighLevel:
await analytics.submitContactForm({
locationId: "your-ghl-location-id",
email: "[email protected]",
subject: "Partnership enquiry",
body: "Tell us more about working together.",
name: "Jane Smith",
source: "website_contact",
tags: ["partnership", "high-value"],
customFields: {
company: "Acme Inc"
},
metadata: {
page: window.location.pathname,
referrer: document.referrer
}
});Required Fields
locationId: Your GoHighLevel location ID- At least one of
emailorphone
Optional Fields
subject,body: Message content (stored asbts_meta_subjectandbts_meta_body)name,firstName,lastName: Contact identitysource: Defaults tobehind_the_scenestags: Array of GHL tags to attachcustomFields: Direct GHL custom field mappingsmetadata: Additional context forwarded asbts_meta_<key>fields
Journey Tracking (Cross-Site Attribution)
Track visitors across external sites and BTS properties:
// Start a funnel journey
const { journeyToken, journeyId, expiresAt } = await analytics.startFunnel("/landing");
// Decorate BTS URLs with journey context
const checkoutUrl = analytics.decorateUrl(
"https://behindthescenes.com/checkout",
journeyToken
);
// Get the persisted token for later use
const token = analytics.getPersistedJourneyToken();
// Notify handoff after visitor reaches BTS
await analytics.notifyHandoff(journeyToken, {
source: "external_landing_page",
campaign: "summer_2024"
});Configuration Options
const analytics = createBTSAnalytics({
siteKey: "required-site-key",
// Auto-tracking configuration
autoTrack: {
pageviews: true, // Initial page_view + SPA navigation
history: true, // pushState/replaceState hooks
outboundLinks: true, // External link clicks
buttonClicks: true, // Button interactions
formSubmissions: true, // Form submit events
search: true, // GET search forms
viewContent: true // IntersectionObserver content views
},
// Or disable all auto-tracking
autoTrack: false,
// Legacy option: disable pageviews (also disables history hooks)
autoPageviews: false,
// Debug mode (logs to console)
debug: false,
// Flush interval in milliseconds (default: 300ms)
flushIntervalMs: 300,
// Google Analytics integration
googleAnalytics: {
loadTag: true,
measurementId: "G-XXXXXXXXXX"
},
// Custom request signing
requestHeaders: async ({ bodyText, path, headers }) => {
return {
...headers,
"X-Signature": await signPayload(bodyText)
};
}
});Auto-Tracking Behavior
autoTrack: false- Disables all automatic trackingautoPageviews: false- Disables initial pageview and SPA navigation- Setting
pageviews: falseinautoTrackobject also disableshistoryhooks - Disabling pageviews prevents duplicate tracking when implementing custom pageview logic
Google Analytics Integration
Detection Mode (Default)
The SDK automatically detects existing GA installations and includes context:
ga_client_id: From_gacookiega_tag_installed: Boolean if gtag detectedga_measurement_id: From script tag
Proxy Mode
Load GA tag for scanner visibility while forwarding events server-side:
const analytics = createBTSAnalytics({
siteKey: "your-site-key",
googleAnalytics: {
loadTag: true,
measurementId: "G-XXXXXXXXXX"
}
});This loads the Google tag with send_page_view: false so BTS can forward events while GA scanners detect an installed tag.
Attribution & UTM Parameters
The SDK automatically captures and persists:
UTM Parameters:
utm_source,utm_medium,utm_campaign,utm_term,utm_content
Click IDs:
fbclid,gclid,gbraid,wbraidli_fat_id,msclkid,ttclid
Cookies:
_fbp,_fbc(Facebook Pixel)
Attribution data is stored in localStorage and attached to every event, preserving the original landing URL and referrer across the session.
Request Signing
Add custom headers for request validation:
const analytics = createBTSAnalytics({
siteKey: "your-site-key",
requestHeaders: async ({ bodyText, headers, path }) => {
const timestamp = new Date().toISOString();
const signature = await signAnalyticsPayload(`${timestamp}:${path}:${bodyText}`);
return {
...headers,
"X-BTS-Analytics-Timestamp": timestamp,
"X-BTS-Analytics-Signature": signature,
};
},
});When requestHeaders is configured, the SDK uses fetch(..., { keepalive: true }) instead of sendBeacon for page-unload events, enabling the same retry semantics for all requests.
Flush Reliability
The SDK queues events and flushes them in batches:
- Automatic flush every 300ms (configurable)
- Immediate flush when queue reaches 50 events
- Failed batches are requeued and retried
- Page unload uses
sendBeacon(orfetchwithkeepalivewhen custom headers are set)
Manual Flush
// Force immediate flush
await analytics.flushNow();Cleanup
Remove event listeners and flush pending events:
analytics.destroy();This:
- Removes click, submit, and navigation listeners
- Disconnects IntersectionObserver and MutationObserver
- Flushes any pending events
- Prevents further event tracking
API Reference
Methods
| Method | Description |
|--------|-------------|
| track(eventName, properties?) | Track a custom event |
| trackStandard(eventName, properties?) | Track a standard event |
| identify(userId, traits?) | Identify the current user |
| page(path?) | Manually trigger a page view |
| startFunnel(entryPath?) | Start a cross-site journey |
| decorateUrl(url, journeyToken?) | Append site key and journey to URL |
| getPersistedJourneyToken() | Get stored journey token |
| notifyHandoff(journeyToken, context?) | Notify BTS of journey handoff |
| submitContactForm(input) | Submit to GoHighLevel |
| flushNow() | Immediately flush event queue |
| listStandardEvents() | Get list of standard event names |
| destroy() | Cleanup and remove listeners |
Types
type BTSAnalyticsStandardEventName =
| "page_view"
| "view_content"
| "search"
| "lead"
| "sign_up"
| "begin_checkout"
| "add_payment_info"
| "purchase"
| "outbound_click"
| "button_click"
| "form_submit";
type BTSAnalyticsEventType = "page_view" | "custom" | "identify" | "conversion";Releases
Publishing the npm package bumps the version with standard semver release types:
patch: Bug fixes and backwards-compatible corrections (e.g.,1.2.3to1.2.4)minor: Backwards-compatible features (e.g.,1.2.3to1.3.0)major: Breaking changes (e.g.,1.2.3to2.0.0)
Run the GitHub Actions release workflow manually and choose the release type to publish the next package version. The workflow reads the latest published npm version, increments it, validates the package, publishes it, and commits the updated package.json version.
For local publishing, set the release type before running the publish script:
RELEASE_TYPE=minor bun run release:publish