Skip to Content
StreamsBrowser SDK

Browser SDK

The Zeotap browser SDK is a lightweight JavaScript library (~2.7 KB gzipped) that captures events from your website and sends them to the Zeotap ingest API. Its API mirrors analytics.js so you can drop it in alongside or replace an existing tracker with minimal changes.

Install

Script Tag

Add this snippet to your website’s <head> to start sending events:

<script>
  !function(){var s=window.zeotap=window.zeotap||[];
  if(!s.initialized){s.methods=["load","track","identify",
  "page","screen","group","alias","setConsent","reset"];
  s.factory=function(m){return function(){
  var a=Array.prototype.slice.call(arguments);a.unshift(m);
  s.push(a);return s}};for(var i=0;i<s.methods.length;i++){
  var m=s.methods[i];s[m]=s.factory(m)}
  var e=document.createElement("script");e.type="text/javascript";
  e.async=!0;e.src="https://content.zeotap.com/composable-sdk/v1/zeotap.min.js";
  var n=document.getElementsByTagName("script")[0];
  n.parentNode.insertBefore(e,n);
  s.load("YOUR_WRITE_KEY",{apiHost:"https://events.zeotap.com"});
  }();
</script>

Replace YOUR_WRITE_KEY with the key from Streams > Event Sources in your workspace. The snippet registers a method queue so any calls made before the main bundle finishes loading are replayed once it is ready. A page view is automatically recorded on initialization (autoPage defaults to true).

You can also copy a pre-filled snippet from Streams > Event Sources in the UI.

API

load(writeKey, options?)

Initialize the SDK. Must be called before any tracking methods. Subsequent calls are no-ops.

OptionTypeDefaultDescription
apiHoststringIngest API host (required). Accepts an absolute URL or a relative path such as "/cdp" when proxying through your own domain — see First-Party Domain Setup
flushAtnumber10Flush the queue after this many events accumulate
flushIntervalnumber5000Flush the queue every N milliseconds
autoPagebooleantrueAutomatically call page() after load()
debugbooleanfalseEnable verbose console logging
firstPartyCookiesbooleanfalsePersist the anonymous ID as a server-set, HttpOnly first-party cookie — survives Safari ITP’s 7-day cap. Requires apiHost to point at a proxy on your own domain (setup guide)
credentials"same-origin" | "include""same-origin"Cookie mode for event requests. Set "include" only when the first-party proxy runs on a separate subdomain
storage"auto" | "cookie" | "localStorage""auto"Where identifiers persist. "auto": first-party cookie on the registrable domain with a localStorage mirror, degrading to localStorage-only when cookies are unusable
cookieDomainstringcomputedCookie domain override (e.g. ".example.com"). Default: the computed registrable domain
cookieExpiryDaysnumber365Identifier cookie expiry in days
cookieSecurebooleantrue on httpsCookie Secure flag
cookieSameSite"Lax" | "Strict" | "None""Lax"Cookie SameSite attribute. "None" forces Secure
nativeAppIdstringSnowflake Native App install id; only needed for write keys created inside a Native App install
consentobjectConsent behavior (opt-in gating, defaults, buffering) — see Consent management

track(event, properties?)

Record a named user action.

zeotap.track("Order Completed", { orderId: "order-456", revenue: 99.99, currency: "USD" });

identify(userId?, traits?)

Associate the current user with traits. The userId is persisted alongside the anonymous ID (see Identity).

zeotap.identify("user-123", { email: "user@example.com", plan: "pro" });

page(category?, name?, properties?)

Record a page view. Called automatically on load() unless autoPage: false.

zeotap.page("Docs", "Getting Started");

screen(category?, name?, properties?)

Record a screen view (mobile-hybrid apps).

zeotap.screen("Onboarding", "Welcome");

group(groupId, traits?)

Associate the current user with a group (organization, team, workspace).

zeotap.group("org-789", { name: "Acme Inc", plan: "enterprise" });

alias(newId, previousId?)

Alias a new user ID to a previous anonymous or identified ID. Useful at sign-up time.

zeotap.alias("user-123");

setConsent(consent)

Set the user’s consent state. All subsequent events carry context.consent with the current state. Pass exactly one of the three forms below — they are mutually exclusive.

TCF v2 consent string (EU/GDPR) — the server automatically derives consent categories from the TCF purpose consent bits:

zeotap.setConsent({ tcfString: "CPXxRfAPXxRfAAfKAB..." });

US Privacy string (CCPA) — the server derives advertising/marketing opt-out status:

zeotap.setConsent({ usPrivacy: "1YNN" });

Pre-parsed categories — use when your CMP already provides resolved boolean values:

zeotap.setConsent({ analytics: true, marketing: false, advertising: false, functional: true, });

Each call replaces the previous consent state entirely. Forwarding rules use these values to gate delivery based on required consent categories, and every event lands in the warehouse with structured consent columns (consent_string, us_privacy, consent_categories).

reset()

Clear the current user identity (user ID, anonymous ID, queue, consent state). Call this on logout so subsequent events are attributed to a new anonymous session.

zeotap.reset();

flush()

Immediately send any queued events. The SDK flushes automatically when flushAt events accumulate or flushInterval elapses, so you usually don’t need to call this — it’s useful before intentionally terminating a session (e.g., right before window.location changes).

How it Works

Identity

  • Anonymous ID — generated on first load() (UUID v4) and persisted in a first-party cookie on the registrable domain (shared across subdomains) with a localStorage mirror, degrading to localStorage-only where cookies are unusable (configurable via storage). Survives across sessions and is only regenerated on reset().
  • User ID — set via identify() and persisted the same way. Every subsequent event carries both IDs when available.

Storage written by JavaScript is subject to browser protections — Safari ITP deletes it after 7 days without a visit. To keep the anonymous ID stable long-term on all browsers, enable firstPartyCookies with a proxy on your own domain: see First-Party Domain Setup.

Queue and flush

Events are buffered in an in-memory FIFO queue. The queue is bounded (500 events by default) — when full, the oldest event is dropped to prevent unbounded memory growth on clients that go offline for long periods. The queue is flushed when one of:

  • flushAt events accumulate (default 10)
  • flushInterval milliseconds pass (default 5 seconds)
  • The page is hidden (pagehide / visibilitychange) — flushed via navigator.sendBeacon so events are delivered even as the user navigates away
  • flush() is called explicitly

Page visibility handling

The SDK uses pagehide and visibilitychange === "hidden" (not beforeunload) so page navigation is reliably captured on mobile Safari and does not block the browser’s Back-Forward Cache. This is the modern, BFCache-friendly approach.

Transport and retry

Each flush sends a POST to the configured apiHost at /v1/batch with an X-Write-Key header. The sendBeacon fallback for page-hide flushes uses a ?writeKey= query parameter instead, because sendBeacon cannot set custom headers. Failed requests are retried up to 3 times with exponential backoff. After exhaustion, the events are dropped and a debug log is emitted if debug: true.

Payload Format

Events follow the Segment spec:

{
  "type": "track",
  "messageId": "uuid-v4",
  "userId": "user-123",
  "anonymousId": "uuid-v4",
  "event": "Order Completed",
  "properties": { "orderId": "456", "revenue": 99.99 },
  "context": {
    "library": { "name": "@zeotap/browser-sdk", "version": "0.1.0" },
    "page": {
      "url": "https://example.com/checkout",
      "title": "Checkout",
      "referrer": "https://example.com/cart",
      "path": "/checkout",
      "search": ""
    },
    "userAgent": "Mozilla/5.0 ...",
    "locale": "en-US",
    "consent": { "tcfString": "CPXx..." }
  },
  "timestamp": "2026-04-10T12:34:56.789Z",
  "sentAt": "2026-04-10T12:34:56.789Z"
}

See Sending events for the full Segment-compatible schema.

Next Steps

Last updated on