Skip to content
Pathbound DOCS

Events — Overview

Events is Pathbound’s built-in event capture. A small JavaScript snippet runs on your website and captures behavioral events — page views, clicks, form submissions, video plays — into the Pathbound platform. Once a visitor identifies (by submitting a form, clicking a tracked email link, or being matched via fingerprint), every event in their history is attached to their contact record.

https://events.pathbound.ai/snippet.js

Sites set up before October 2026 load https://tracker.pathbound.ai/tracker.js. That address keeps working unchanged, so there is nothing to update.

The script is served minified and gzipped/brotli-compressed, around 5 KB on the wire. There are no client-side dependencies; everything runs in vanilla browser JS.

Out of the box, no configuration:

  • page_view on every navigation, with title, path, UTM params, and load time.
  • first_visit the first time a browser is seen.
  • button_click on every <button> click — text, class, id.
  • link_click on every <a> click — text, href, internal vs external.
  • form_submit on every native <form> submission — the form’s DOM id, class, action and method. Never the field values.
  • Video events when a <wistia-player> embed is on the page — video_play, video_pause, video_complete, video_milestone (25/50/75/100%), video_conversion (in-video email capture).

Every event is enriched server-side with the visitor’s identity (cookie + fingerprint), session, referrer, screen resolution, user agent, and _ga user ID if Google Analytics is present.

Four mechanisms, in order of reliability:

  1. Server-side identify — When a user logs in, call POST /v1/identify from your backend with their external ID + the pathbound_visitor_id. This is the authenticated path: it upserts the contact, merges traits, and back-fills their prior anonymous events immediately.
  2. Client-side identify — Call window.pathbound.identify(externalId, traits) at login (no backend or API key needed). It sets the external-contact cookie, records an identify event, and creates or links the contact via the domain-gated public identify endpoint.
  3. External contact ID cookie — If your app sets pathbound_external_contact_id itself (the client identify() call does this for you), the snippet reads it on every event and the backend resolves it to a Pathbound contact.
  4. Form submission — When your form posts to the public form endpoint with the visitor’s visitor_id, the submission creates or updates a contact by email and ties that visitor to it. The snippet does not do this for you: see below.

Plus fingerprint recovery underneath them all — if the visitor’s pathbound_visitor_id cookie is missing (cleared, new device, incognito), the snippet computes a device fingerprint from Canvas + WebGL + AudioContext + system signals, hashes it (SHA-256), and asks the server for the canonical visitor ID. This recovers the timeline across cookie loss without depending on third-party identifiers.

Details: Identify logged-in users and Identity & fingerprinting.

Two events have similar names and mean different things.

  • form_submit is the snippet noticing a submit on a page. It carries only the form’s DOM id, class, action and method. It holds none of the field values, so it identifies nobody and creates no contact.
  • form_submission is written by the API when a form posts to the public form endpoint and the submission is stored. It is the one tied to a contact: the endpoint creates or updates the contact from the submitted email, and the event carries the ids of that contact and of the stored submission.

To capture what a visitor typed into a form, post the form to the public form endpoint. Loading the snippet on the page is not enough. Payloads: form_submit and form_submission.

  • Honors a Do-Not-Track cookie — set pathbound_dnt=1 on a domain and the snippet exits before doing anything.
  • Does not act on the browser’s Do Not Track or Global Privacy Control signals. navigator.doNotTrack is read only as one of the fingerprint signals (so a DNT browser produces a different fingerprint than the same device without).
  • Stores cookies with SameSite=Strict; Secure over HTTPS.
  • No third-party requests — events POST to Pathbound’s own ingest host: events.pathbound.ai for the current snippet, tracker.pathbound.ai for installs from before October 2026.
  • Masks credentials and emails in captured URLs before anything is stored. url, referrer and the landing-page fields keep their path and parameter names, but the values of OAuth callback code/state, tokens, passwords, API keys, signatures (including presigned S3/GCS links), email addresses, JWTs and 64+ character hex strings are replaced with [redacted], anywhere in the query, the fragment or a URL nested inside a parameter (returnTo=…). A token in the path is masked too: after a page named for a link sent by email (/reset_password/…, /verify/…, /confirm/…, /invite/…, /unsubscribe/… and the like), any later segment of six or more characters that does not read as words (a signed value:timestamp:signature included), and on any page a segment with 32 or more generated characters. Record ids stay as they are (/map/<32 hex>, a UUID, a ULID), so a page keeps its identity. A URL with nothing to mask is stored exactly as captured, so URL filters and group-bys keep working. This applies to events, including their path fields (path, previousPath, landingPath, pagePath) and the campaign values in utmParams, and to a contact’s first-touch landing page and referrer.

Event ingestion is a standalone Express service that:

  • Serves the snippet (rate-limited, host-validated, brotli/gzip).
  • Accepts batched events at POST /track (up to 10 events per batch, 2-second timeout).
  • Provides fingerprint recovery at POST /recover-visitor.
  • Writes directly to the same MongoDB as the REST API and MCP server, so events appear in GET /v1/events immediately.

This means event ingestion is not authenticated by API key — instead, the server validates the request’s Origin/Referer against the verified domains of each tenant. You verify domains in Settings → Domains in the dashboard.