Skip to content
Pathbound DOCS

Tracker — Identity & fingerprinting

The tracker resolves “is this the same visitor I saw before?” through three layers, each more durable than the last:

  1. The pathbound_visitor_id cookie.
  2. Device fingerprint recovery when the cookie is missing.
  3. 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.

CookieLifetimeScopePurpose
pathbound_visitor_id365 dayspath=/; SameSite=Strict; SecureStable per-browser identifier.
pathbound_session_id1 day, slidingpath=/; SameSite=Strict; Secure; HttpOnlyActive browsing session. Refreshes on activity.
_sellably_visitor_id (mirror)365 dayssame as visitor_idBackward-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)”
CookiePurpose
pathbound_external_contact_idSet 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_idBackward-compat fallback for the same.
_gaGoogle Analytics client ID — captured into the event as ga_user_id so you can correlate Pathbound events with GA.
pathbound_dntIf =1, the tracker exits at the very top of the script and does nothing.

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.

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.platform
  • navigator.cookieEnabled, navigator.doNotTrack, navigator.hardwareConcurrency
  • screen.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 OfflineAudioContext rendering — 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.

  1. The tracker computes the fingerprint hash.
  2. Checks if pathbound_visitor_id is already set — if so, ship a _fingerprint_update event to refresh the server-side mapping and continue.
  3. If no cookie, POST /recover-visitor with { fingerprint }.
  4. If the server returns a known visitorId, set it as the cookie.
  5. 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.

  • 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_dnt opt-out — DNT visitors don’t fingerprint.

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_contact_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.

The external_contact_id is a soft identity assertion — the cookie path is not authenticated, so treat the value as a join key, not a secret, and make it opaque and unguessable (a UUID or random token). Never use a sequential integer, an email address, or anything an attacker could enumerate: a logged-in user can edit their own cookie, and a guessable ID lets them attribute their browsing to someone else’s contact. Pathbound never returns contact data to the browser, so a forged ID can’t read another profile — but an unguessable ID closes the write-side (timeline-poisoning) gap entirely.

For a tamper-proof binding — authenticated, trait-carrying, and able to create a profile on the spot — use the server-side POST /v1/identify call. That’s the recommended path; see the Identify logged-in users quickstart for the full setup (cookie + API, logout handling, and the one-device-one-contact rule).

When an event arrives, the backend tries to identify the visitor in this order:

  1. external_contact_id in the event payload → matches a Pathbound contact directly.
  2. Form submission email → matches a contact by properties.email, creating one if the form is configured for upsert.
  3. visitor_id that has a prior identified event → joins to that contact via the events collection.
  4. No identification → event is recorded with internal_contact_id: null. It’s still queryable (set identified_only=false in /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.