Tracker — Identity & fingerprinting
The tracker resolves “is this the same visitor I saw before?” through three layers, each more durable than the last:
- The
pathbound_visitor_idcookie. - Device fingerprint recovery when the cookie is missing.
- External contact ID when your application has authenticated the user.
Plus a session cookie that scopes events to a single browsing session, and a Google Analytics user ID capture that lets you join Pathbound events with GA reports.
Cookies the tracker sets
Section titled “Cookies the tracker sets”| Cookie | Lifetime | Scope | Purpose |
|---|---|---|---|
pathbound_visitor_id | 365 days | path=/; SameSite=Strict; Secure | Stable per-browser identifier. |
pathbound_session_id | 1 day, sliding | path=/; SameSite=Strict; Secure; HttpOnly | Active browsing session. Refreshes on activity. |
_sellably_visitor_id (mirror) | 365 days | same as visitor_id | Backward-compat duplicate (legacy SaaS name). |
The session cookie is HttpOnly so it’s not readable from JS; the visitor cookie is intentionally JS-readable so SPA code can fire identified events.
Cookies the tracker reads (but does not set)
Section titled “Cookies the tracker reads (but does not set)”| Cookie | Purpose |
|---|---|
pathbound_external_contact_id | Set by your application when the user has authenticated. The tracker forwards this on every event so the backend can resolve it to a Pathbound contact. |
_sellably_external_contact_id | Backward-compat fallback for the same. |
_ga | Google Analytics client ID — captured into the event as ga_user_id so you can correlate Pathbound events with GA. |
pathbound_dnt | If =1, the tracker exits at the very top of the script and does nothing. |
Sessions
Section titled “Sessions”A session is a single browsing window. The session cookie stores <id>|<last-activity-ms>. On every event:
- If the cookie is missing, a new session id is minted and a 1-day cookie is set.
- If the last activity was more than 30 minutes ago, the old session ends and a new one begins.
- Otherwise the cookie’s last-activity timestamp is bumped, extending the session.
This is the same heuristic GA4 uses: a session is what a human would call a “visit,” not what the cookie engine treats as a single connection.
Device fingerprinting
Section titled “Device fingerprinting”When a visitor arrives without a pathbound_visitor_id cookie, the tracker attempts to recover their canonical visitor ID from the device fingerprint before minting a new one.
The fingerprint is a SHA-256 hash of:
navigator.userAgent,navigator.language,navigator.platformnavigator.cookieEnabled,navigator.doNotTrack,navigator.hardwareConcurrencyscreen.width × height,screen.colorDepth,window.devicePixelRatio- Timezone offset (
new Date().getTimezoneOffset()) - A canvas rendering of “Device fingerprint” text in 14 px Arial — the canonical browser-fingerprint canvas trick
- The WebGL UNMASKED_VENDOR + UNMASKED_RENDERER strings (when WEBGL_debug_renderer_info is exposed)
- An
OfflineAudioContextrendering — sums the absolute amplitudes of the audio output, which depends on the device’s audio stack
If the browser doesn’t support crypto.subtle.digest, the tracker falls back to a 64-bit DJB2-style hash.
Recovery flow
Section titled “Recovery flow”- The tracker computes the fingerprint hash.
- Checks if
pathbound_visitor_idis already set — if so, ship a_fingerprint_updateevent to refresh the server-side mapping and continue. - If no cookie,
POST /recover-visitorwith{ fingerprint }. - If the server returns a known
visitorId, set it as the cookie. - Otherwise, mint a fresh UUID and treat this as a new visitor — fire
first_visit.
This means a user clearing cookies or switching browsers on the same machine generally lands on the same visitor_id, with all of their prior history intact.
What it does NOT do
Section titled “What it does NOT do”- It does not identify the user by name, email, or any PII — the fingerprint is one-way.
- It does not persist data outside the cookie + the server-side fingerprint mapping.
- It does not ship raw fingerprint signals — only the SHA-256 hash leaves the browser.
- It does not override the
pathbound_dntopt-out — DNT visitors don’t fingerprint.
External contact IDs
Section titled “External contact IDs”When your application authenticates a user, set a cookie:
// On login (or any time you know the user's Pathbound contact ID):document.cookie = 'pathbound_external_contact_id=' + contact.externalId + '; path=/; max-age=31536000; SameSite=Strict; Secure';The tracker reads this on every event and includes it in the payload. The backend resolves it against the contact’s stored external_id (or your custom property mapping) and stitches the event timeline together — even across visitor cookies and devices.
This is the most reliable identification mechanism. Use it whenever you can.
Identification cascade
Section titled “Identification cascade”When an event arrives, the backend tries to identify the visitor in this order:
external_contact_idin the event payload → matches a Pathbound contact directly.- Form submission email → matches a contact by
properties.email, creating one if the form is configured for upsert. visitor_idthat has a prior identified event → joins to that contact via the events collection.- No identification → event is recorded with
internal_contact_id: null. It’s still queryable (setidentified_only=falsein/v1/events), and will retroactively join to a contact on the next identification.
Anonymous events are not lost; they sit waiting until a future identification event reveals who they belonged to all along.