Events — Event types
The snippet fires events automatically with no configuration. Each event is wrapped in a standard envelope on the wire and lands in your tenant’s events collection ready for GET /v1/events.
Event envelope
Section titled “Event envelope”Every event arrives at the backend with this shape (the snippet batches up to 10 events per request):
{ "client_id": "3f2b8c1e-9a4d-4e7f-8b21-6c0d5e9fa713", "event": "page_view", "data": { /* event-specific payload */ }, "timestamp": "2026-04-29T11:23:45.000Z", "url": "https://example.com/pricing", "domain": "example.com", "visitorId": "vis_abc", "sessionId": "sess_xyz", "external_contact_id": null, "ga_user_id": "GA1.1.1234.5678", "userAgent": "Mozilla/5.0 …", "referrer": "https://google.com/", "screenResolution": "1920x1080"}client_id is the snippet’s own ID for the event, the same every time its batch is sent, so a batch that arrives twice is stored once. It isn’t returned by GET /v1/events.
Server-added fields
Section titled “Server-added fields”The backend derives extra fields from the userAgent, referrer and url every event already carries — no extra snippet config. They’re returned alongside each event by GET /v1/events and GET /v1/events/:id, by the agent tools that read those (list_events with fields="full", get_event), and platform, os and device_type are in the webhook payload:
browser,browser_version,os,device_type(Desktop/Mobile/Tablet) — derived fromuserAgent.platform(web/ios/android) — where the event was captured: a browser, or a native app’s webview. It answers whatoscan’t: Safari on an iPhone and your iOS app both reportos: "iOS", and only the app isplatform: "ios". Derived from the event’surlanduserAgent:ios— the page runs from acapacitor://URL, which is how Capacitor serves an iOS app. This holds on an iPad too, where the webview’suserAgentis a desktop Mac’s.android— theuserAgentis an Android WebView: the page is running inside an Android app. The in-app browsers we recognize (Facebook, Instagram, Threads, LinkedIn, X, TikTok, Snapchat, Pinterest, Line, WeChat, KakaoTalk, Naver, Bing and the Google app) stayweb. Any other app’s webview reportsandroid, so a page of yours embedded in someone else’s Android app counts too.web— everything else, phone browsers included. An iOS app that loads its pages overhttps://also reportsweb.
Events that did not come from the snippet (integration events, and custom events sent through the API) have no
platform. Snippet events stored before the field was introduced have been filled in with the same rule.referring_domain— the host ofreferrer, or"direct"when there’s no referrer.is_bot— present (andtrue) only when theuserAgentmatches a known bot/crawler. Bot events are flagged, not dropped, so you can include or exclude them at read time (filteris_bot == trueto find them, oris_bot != trueto exclude). Absent on human traffic.
device_type, browser, os, platform, referring_domain, and is_bot — along with visitor_id, session_id, and the element data fields (text, element_type, element_id, class) — are available as group_by dimensions and filters in aggregate_data and GET /v1/events (e.g. event counts by device type, or distinct visitors per button label).
The first referrer / referring domain / landing page / UTMs seen for a visitor are also persisted to their contact as first-touch attribution — creation_referrer, creation_referring_domain, creation_landing_page, creation_utms (surfaced grouped under first_touch by get_contact_details). They are written once and never overwritten by later visits.
How values are stored
Section titled “How values are stored”Values are stored as the snippet sent them. Nothing is rewritten or re-encoded. Only these rules apply on the way in:
- Keys starting with
$and the keys__proto__,constructorandprototypeare dropped, and control characters are removed from keys. - Credential and email values inside captured URLs are replaced with
[redacted](see Privacy). - Size caps:
urlandreferrer2,048 characters,userAgent2,000, strings insidedata1,000; at most 50 keys per object and 100 items per array; objects nested more than 5 levels deep insidedatabecomenull.
Because values arrive from browsers, treat them as untrusted input: escape them when you render HTML, and check a url’s scheme before you use it as a link.
The fields below describe the data payload for each event.
Page lifecycle
Section titled “Page lifecycle”page_view
Section titled “page_view”Fires on the initial document load and on every client-side route change. For single-page apps the snippet patches history.pushState/replaceState and listens for popstate/hashchange; a route counts as a new view when pathname + search + hash changes (an identical repeated URL is ignored).
navigationTypeis"load"on the first paint and"spa"for in-app navigations.previousPathis present on"spa"views and holds the path the visitor came from.urlis the full URL of the page the view is for, query string included (credential and email values are masked; see Privacy).utmParamsalso captures ad-click IDs when present in the URL:gclid,fbclid,msclkid,li_fat_id,ttclid,gbraid,wbraid. The first set seen for a visitor is persisted to their contact as first-touch attribution.
{ "title": "Pricing — Example", "path": "/pricing", "url": "https://example.com/pricing?plan=team", "navigationType": "spa", "previousPath": "/", "utmParams": { "utm_source": "google", "utm_medium": "cpc", "utm_campaign": "spring-2026" }, "userAgent": "…", "referrer": "…", "screenResolution": "1920x1080", "pageLoadTime": 412}On the very first load this event carries
navigationType: "load"and nopreviousPath.
page_leave
Section titled “page_leave”Fires for the page a visitor is leaving — on each SPA route change (for the page being navigated away from) and once on exit (pagehide / beforeunload). timeOnPage is the dwell time on that page in milliseconds. url and path always describe the page being left, never the next one.
{ "title": "Pricing — Example", "path": "/pricing", "url": "https://example.com/pricing?plan=team", "timeOnPage": 8423}first_visit
Section titled “first_visit”Fires once per browser, the first time the snippet assigns a fresh pathbound_visitor_id (i.e. no recoverable visitor existed for the device fingerprint).
{ "landingPage": "https://example.com/pricing", "landingPath": "/pricing", "initialReferrer": "https://google.com/", "utmParams": { /* same as page_view */ }, "title": "Pricing — Example", "userAgent": "…", "screenResolution": "1920x1080", "pageLoadTime": 410}User interactions
Section titled “User interactions”button_click
Section titled “button_click”Fires when a click resolves to a <button>, <input type="submit|button">, or any element with role="button" (including <a role="button"> — an explicit role wins over the tag) — including clicks that land on a child element (an icon or <span> inside the button). The captured text/class/id are the actionable element’s, not the child’s.
{ "text": "Get a demo", "class": "btn btn-primary", "id": "demo-cta", "elementType": "button"}Filter and aggregate these interactions by any data field via GET /v1/events and aggregate_data: text (exact) / text_contains, element_type, element_id, and class_contains. For elements with no visible text — icon buttons, where text and often id are empty — class_contains with a stable class token (e.g. MuiIconButton-root, not a hashed css-* / jss* token) is the way to target them. To list the contacts who clicked a given element, use the matching has_event_* filters on GET /v1/contacts.
link_click
Section titled “link_click”Fires when a click resolves to an <a> (without role="button") or any element with role="link", including clicks on a child element inside the link. isExternal is true if the link has an href whose hostname differs from the current page’s hostname.
{ "text": "Read the docs", "href": "https://pathbound.ai/docs", "class": "footer-link", "id": "", "elementType": "link", "isExternal": true}form_submit
Section titled “form_submit”Fires on every native <form> submit. Form field values are not captured here — post the form to the public form endpoint for that. This event is the snippet noticing a submit; it is not form_submission, which the API writes when a submission is stored.
{ "formId": "demo-form", "formClass": "contact-form", "action": "/submit", "method": "post"}Identification
Section titled “Identification”identify
Section titled “identify”Fires when your code calls window.pathbound.identify(externalId, traits) — the only tracked event you trigger explicitly. It marks the login moment on the visitor’s timeline and carries the asserted identity; the actual contact create-or-link happens via the public identify endpoint the same call makes.
{ "external_contact_id": "usr_8f3a2c9e-…", "traits": { "email": "ada@example.com" }}traits is present only when passed. Calls with placeholder ids ("undefined", "null", "anonymous", …) are ignored client-side and fire nothing.
Auto-detected when a <wistia-player> embed is on the page (or added later via DOM mutation).
video_play
Section titled “video_play”Fires when playback begins or resumes.
{ "provider": "wistia", "mediaId": "abc123def", "videoName": "Product overview", "duration": 187, "currentTime": 0, "pageTitle": "Home — Example", "pagePath": "/", "wistiaVisitorKey": "v1-…"}video_pause
Section titled “video_pause”Fires on pause, including when the user navigates away.
{ "provider": "wistia", "mediaId": "abc123def", "videoName": "Product overview", "duration": 187, "currentTime": 64, "secondsWatched": 58, "pageTitle": "Home — Example", "pagePath": "/"}video_complete
Section titled “video_complete”Fires when the video reaches its natural end.
{ "provider": "wistia", "mediaId": "abc123def", "videoName": "Product overview", "duration": 187, "secondsWatched": 187, "pageTitle": "Home — Example", "pagePath": "/"}video_milestone
Section titled “video_milestone”Fires once each at 25%, 50%, 75%, and 100% of duration. The 100% milestone may fire alongside video_complete.
{ "provider": "wistia", "mediaId": "abc123def", "videoName": "Product overview", "milestone": 50, "currentTime": 94, "duration": 187, "pageTitle": "Home — Example", "pagePath": "/"}video_conversion
Section titled “video_conversion”Fires when an in-video email-capture form is submitted.
{ "provider": "wistia", "mediaId": "abc123def", "videoName": "Product overview", "duration": 187, "currentTime": 32, "pageTitle": "Home — Example", "pagePath": "/", "conversionType": "email_only", "email": "ada@example.com", "firstName": "Ada", "lastName": "Lovelace"}The submitted email is the most reliable identifier the snippet captures from in-page interactions. If the visitor isn’t already linked to a contact, video_conversion is what flips them from anonymous to identified.
Written by the API
Section titled “Written by the API”form_submission
Section titled “form_submission”Not fired by the snippet. The API writes it when a form posts to the public form endpoint and the submission is stored. It is linked to the contact the submission created or updated, so it shows on that contact’s timeline, and it is delivered to HTTPS event streams like any other event.
{ "form_id": "playbook", "submission_id": "6703f1c2a4b5c6d7e8f90123", "contact_id": "6703f1c2a4b5c6d7e8f90456", "company_id": "6703f1c2a4b5c6d7e8f90789", "contact_created": true, "company_created": false, "source_domain": "www.example.com"}form_idis the id in the URL the form posted to.submission_idis the id of the stored submission.contact_idandcompany_idare the records the submission created or updated. Each is missing ornullwhen the submission carried nocontact_dataor no usablecompany_data.contact_createdandcompany_createdaretruewhen the submission created the record andfalsewhen it updated one.source_domainis the host of the page the form posted from.
Those seven keys are the whole payload. It never holds the submitted field values, and it holds no page URL. On the envelope, domain is the same host, and url, visitorId, sessionId, userAgent, referrer, screenResolution and platform are absent, because no browser event was recorded.
The event is written only for a form id that fits the form id rule. An email automation is triggered by the stored submission, never by this event, so an event with this name that arrives another way sends nothing.
System events
Section titled “System events”_fingerprint_update
Section titled “_fingerprint_update”Internal — fired when the snippet computes a fresh device fingerprint. Used by the server to maintain the fingerprint → visitor ID mapping. Most consumers can ignore this event type when reading /v1/events.