JavaScript tag events

Complete reference of every web event generated by the JavaScript tag.

Envelope structure

Every event stored in ClickHouse (t130_replays_metrics_v3_web_events) shares the same top-level envelope:

 
{
  "dt": 1772115228563,
  "event": "<event-name>",
  "payload": { ... },
  "session_id": "551790ef-...",
  "anonymous_user_id": "671c8bdc-...",
  "user": { "email": "user@example.com" },
  "fingerprint": ["v1_<hash>"],
  "_app_id": 4153,
  "_ip_addresses": ["94.230.145.34"],
  "contexts": { ... }
}
Field Type Notes
dt Number Unix timestamp (ms) when the event was captured client-side
event String Event type name (see sections below)
payload Object Event-specific data
session_id UUID Session identifier, rotated after session_timeout_ms of inactivity
anonymous_user_id UUID Stable anonymous user identifier persisted in localStorage
user Object? Present only if betterstack('user', {...}) was called. Contains whatever was passed.
fingerprint Array? Present only if FingerprintJS is configured. Array with one "v1_<hash>" string.
_app_id Number Internal app identifier (added server-side)
_ip_addresses Array Client IP addresses (added server-side)
contexts Object? Browser/OS/device info. Present on autocapture events only (not on memory, user-agent-specific-memory, session-start, or custom events).

contexts object (when present)

 
{
  "browser": { "type": "browser", "browser": "Chrome 145", "name": "Chrome", "version": "145.0.0.0" },
  "client_os": { "type": "os", "os": "macOS 10.15.7", "name": "macOS", "version": "10.15.7" },
  "device": { "type": "unknown" }
}

Shared payload.meta (autocapture events only)

All autocapture events (click, dblclick, contextmenu, tap, input, change, submit, reset, page-load, page-change, page-leave, rage-click) include this meta block in payload:

 
{
  "title": "Page Title",
  "timestamp": 1772115228563,
  "timezone": -60,
  "url": "https://example.com/page",
  "userAgent": "Mozilla/5.0 ...",
  "referrer": "https://example.com/previous",
  "screen": { "width": 3008, "height": 1692 },
  "window": { "width": 1504, "height": 1528 },
  "devicePixelRatio": 2,
  "language": "en",
  "platform": "MacIntel",
  "isMobile": false,
  "isTouch": false,
  "isBot": false
}

Event types

Event Plugin Description
session-start SessionConfigGenerator Marks the beginning of a new session. Includes UTM params, referrer, and fingerprint data.
page-load PageViewPlugin Initial full page load (window.load).
page-change PageViewPlugin In-app SPA navigation (pushState, replaceState, popstate, hashchange).
page-leave PageViewPlugin Tab close or full navigation away (beforeunload).
click TapPlugin Left-click on non-touch devices.
dblclick TapPlugin Double-click on non-touch devices.
contextmenu TapPlugin Right-click (all devices).
tap TapPlugin Touch start on touch devices.
rage-click RageClickPlugin 5+ clicks within 8px / 2s — indicates user frustration.
input FormPlugin Keystroke or value change in a form element.
change FormPlugin Form element value committed (blur after change, or select/checkbox toggle).
submit FormPlugin Form submission.
reset FormPlugin Form reset.
memory MemoryConfigGenerator JS heap snapshot via performance.memory (Chrome-only), polled every 15s.
user-agent-specific-memory MemoryConfigGenerator Detailed memory breakdown via performance.measureUserAgentSpecificMemory(), polled every 60s.
<custom> track API Any event sent via betterstack('track', name, data).

Events

session-start

Source: SessionConfigGenerator (session_config_generator.rb) Trigger: First event in a new session, or after session timeout. Fired lazily — queued when the first trackable event occurs, not on page load itself. If FingerprintJS is configured, waits for fingerprint resolution (up to 500ms) before sending.

The session is new when there is no existing sid in localStorage, or when the time since last activity exceeds session_timeout_ms.

 
{
  "event": "session-start",
  "payload": {
    "utm_source": null,
    "utm_medium": null,
    "utm_campaign": null,
    "utm_term": null,
    "utm_content": null,
    "referrer": "https://google.com/",
    "gclid": null,
    "fbclid": null,
    "msclkid": null,
    "ttclid": null,
    "li_fat_id": null,
    "twclid": null,
    "page_url": "https://example.com/landing",
    "fingerprint_data": { "...": "..." }
  }
}

fingerprint_data is only present when FingerprintJS is enabled; contains raw component data.

No contexts field on this event.

page-load

Source: PageViewPlugin (autocapture-plugins.js) Trigger: window.load event (fires once per full page load). Config: Enabled when autocapture_enabled? and autoPageview !== false.

 
{
  "event": "page-load",
  "payload": {
    "event": "page",
    "timestamp": "2026-02-26T14:13:48.563Z",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "details": {
      "type": "page-load",
      "url": "https://example.com/"
    }
  }
}

Note: meta.referrer is the browser's document.referrer (the previous page that linked here).

page-change

Source: PageViewPlugin (autocapture-plugins.js) Trigger: In-app navigation via: - history.pushState() (SPA routers: React Router, Vue Router, Next.js, Turbo) - history.replaceState() (only if URL actually changes) - popstate (browser back/forward) - hashchange (hash-based routing)

Deduplicated: skipped if URL hasn't changed since last capture. Config: Same as page-load.

 
{
  "event": "page-change",
  "payload": {
    "event": "page",
    "timestamp": "2026-02-26T14:13:48.589Z",
    "meta": { "...shared meta...", "referrer": "https://example.com/previous-spa-url" },
    "session": "551790ef-...",
    "details": {
      "type": "page-change",
      "url": "https://example.com/new-page"
    }
  }
}

Note: meta.referrer is the previous SPA URL (not document.referrer). The onEventCapture callback in AutocaptureConfigGenerator moves details.referrermeta.referrer.

page-leave

Source: PageViewPlugin (autocapture-plugins.js) Trigger: window.beforeunload event (tab close, full navigation away, page refresh). Config: Same as page-load.

 
{
  "event": "page-leave",
  "payload": {
    "event": "page",
    "timestamp": "...",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "details": {
      "type": "page-leave",
      "url": "https://example.com/current-page"
    }
  }
}

Special behavior: page-leave skips _ensureSessionStarted() — it won't create a new session on its own.

click

Source: TapPlugin (autocapture.js) Trigger: document.click event on non-touch devices (!('ontouchstart' in window)). Config: Enabled when autocapture_enabled?.

 
{
  "event": "click",
  "payload": {
    "event": "click",
    "timestamp": "...",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "target": {
      "selector": "a",
      "attributes": { "href": "https://example.com/about" }
    },
    "details": {
      "type": "click",
      "x": 652,
      "y": 89
    }
  }
}

target.selector is the element tag name. target.attributes contains whitelisted attributes configured in AutocaptureConfigGenerator: value, type, href, src, id, name, placeholder, title, alt, role.

dblclick

Source: TapPlugin (autocapture.js) Trigger: document.dblclick event on non-touch devices. Config: Same as click.

Same payload structure as click, but details.type = "dblclick" and payload.event = "click" (the plugin key).

contextmenu

Source: TapPlugin (autocapture.js) Trigger: document.contextmenu event (right-click). Fires on all devices (no touch guard). Config: Same as click.

Same payload structure as click, but details.type = "contextmenu".

tap

Source: TapPlugin (autocapture.js) Trigger: document.touchstart event on touch devices. Config: Same as click.

Same payload structure as click, but details.type = "tap" and coordinates come from the touch event. Only fires on touch-capable devices.

rage-click

Source: RageClickPlugin (autocapture-plugins.js) Trigger: 5+ clicks within 8px radius over 2 seconds. Detected by monitoring all document.click events and calculating centroid clustering. Config: Enabled when autocapture_enabled? AND rage_clicks_enabled?.

 
{
  "event": "rage-click",
  "payload": {
    "event": "rage-click",
    "timestamp": "...",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "details": {
      "type": "rage-click",
      "clickCount": 5,
      "firstClickAt": 1772115228000,
      "lastClickAt": 1772115229500,
      "centroid": { "x": 652, "y": 89 }
    }
  }
}

After detection, the click buffer is reset.

input

Source: FormPlugin (autocapture.js) Trigger: document.input event (fires on every keystroke/value change in form elements). Config: Enabled when autocapture_enabled?.

 
{
  "event": "input",
  "payload": {
    "event": "form",
    "timestamp": "...",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "target": {
      "selector": "input",
      "attributes": { "type": "text", "id": "name", "name": "name", "placeholder": "Your name" }
    },
    "details": {
      "type": "input"
    }
  }
}

Note: the value attribute is captured for select elements but the actual typed text in text inputs is NOT captured (only the element attributes are collected via the safelist).

change

Source: FormPlugin (autocapture.js) Trigger: document.change event (fires when a form element loses focus after its value changed, or on select/checkbox/radio change). Config: Same as input.

Same structure as input, but details.type = "change". For select elements, target.attributes.value reflects the newly selected value.

submit

Source: FormPlugin (autocapture.js) Trigger: document.submit event (form submission). Config: Same as input.

 
{
  "event": "submit",
  "payload": {
    "event": "form",
    "timestamp": "...",
    "meta": { "...shared meta..." },
    "session": "551790ef-...",
    "target": {
      "selector": "form",
      "attributes": { "id": "contact-form", "name": "contact" }
    },
    "details": {
      "type": "submit",
      "action": "https://example.com/api/submit",
      "method": "post",
      "id": "contact-form",
      "name": "contact"
    }
  }
}

details includes form metadata (action, method, id, name) extracted from target.form.

reset

Source: FormPlugin (autocapture.js) Trigger: document.reset event (form reset button clicked). Config: Same as input.

Same structure as submit, but details.type = "reset".

memory

Source: MemoryConfigGenerator (memory_config_generator.rb) Trigger: Polled every 15 seconds via setInterval. Also fires once immediately on init. Uses performance.memory (Chrome-only, non-standard). Config: Enabled when memory_enabled?.

 
{
  "event": "memory",
  "payload": {
    "memory": {
      "jsHeapSizeLimit": 4294967296,
      "totalJSHeapSize": 4078952,
      "usedJSHeapSize": 2979308
    },
    "userAgent": "Mozilla/5.0 ...",
    "meta": { "url": "https://example.com/page" }
  }
}

No contexts field. Completely different payload structure from autocapture events — no event, timestamp, session, target, or details fields inside payload.

user-agent-specific-memory

Source: MemoryConfigGenerator (memory_config_generator.rb) Trigger: Polled every 60 seconds via setInterval. Also fires once immediately on init. Uses performance.measureUserAgentSpecificMemory() which requires crossOriginIsolated context (COOP/COEP headers). Config: Same as memory.

 
{
  "event": "user-agent-specific-memory",
  "payload": {
    "bytes": 1234567,
    "breakdown": [
      {
        "bytes": 1000000,
        "attribution": [{ "url": "https://example.com/", "scope": "Window" }],
        "types": ["JavaScript"]
      }
    ],
    "userAgent": "Mozilla/5.0 ...",
    "meta": { "url": "https://example.com/page" }
  }
}

No contexts field. The bytes and breakdown fields come directly from the performance.measureUserAgentSpecificMemory() API result, spread into the payload.

Custom events

Source: betterstack('track', '<name>', <data>) API call Trigger: Application code calling the track function directly. Config: Always available once the JS tag is loaded and init'd.

 
{
  "event": "<custom-event-name>",
  "payload": { "...whatever was passed as data..." }
}

No contexts field. The payload is the raw data object passed to track().

Event flow and filtering

Events pass through betterstack.track() (script_generator.rb:156-184) which applies these filters before queueing:

  1. Sampling: session.trackEvents must be true (determined at session creation by events_sample_rate)
  2. Analytics events (page-load, page-leave, page-change): allowed if collect_analytics is enabled, regardless of identified/anonymous settings
  3. Identified events: allowed only if collect_identified is enabled (when betterstack._user is set)
  4. Anonymous events: allowed only if collect_anonymous is enabled (when no user is set)

Special cases: - session-start and page-leave skip the _ensureSessionStarted() call to avoid infinite recursion / session creation on exit

Plugin → event mapping

Plugin Key Events Source file
TapPlugin tap click, dblclick, contextmenu, tap autocapture.js
FormPlugin form input, change, submit, reset autocapture.js
PageViewPlugin page page-load, page-change, page-leave autocapture-plugins.js
RageClickPlugin rage-click rage-click autocapture-plugins.js
(session manager) session-start session_config_generator.rb
(memory monitor) memory memory_config_generator.rb
(memory monitor) user-agent-specific-memory memory_config_generator.rb
(track API) <custom> script_generator.rb

Configuration flags

Flag Controls
autocapture_enabled? All autocapture events (click, dblclick, contextmenu, tap, input, change, submit, reset, page-load/change/leave)
rage_clicks_enabled? rage-click event only
memory_enabled? memory and user-agent-specific-memory events
analytics_enabled? Whether page-load/change/leave pass the track filter
anonymous_events_enabled? Whether anonymous user events pass the track filter
identified_events_enabled? Whether identified user events pass the track filter
autoPageview (user config) Whether PageViewPlugin is registered (can be disabled via betterstack('config', { autoPageview: false }))