Tracker — Identify logged-in users
The tracker captures anonymous browsing the moment it loads. Identifying a user connects that anonymous history to a known person — so when someone logs in, every page view they made before signing up gets attached to their contact, and everything they do afterward lands on the same unified timeline.
There are two ways to identify, and the most reliable setup uses both — they’re complementary:
| What it is | Trust | When it resolves | |
|---|---|---|---|
POST /v1/identify | Authenticated server call at login | Your API key asserts the identity | Immediately (creates the contact + back-fills) |
pathbound_external_contact_id cookie | A cookie your app sets at login | Soft (client-set) | Every event, at ingest |
Use /v1/identify as the authoritative call and the cookie for continuous real-time attribution. If you only have a backend, /v1/identify alone is enough; if you only have a static site, the cookie alone works.
Before you start
Section titled “Before you start”- The tracker snippet is installed and your domain is verified.
- You have an API key with the
contacts:writescope (Settings → REST API) — needed for/v1/identify. - You’ve chosen a stable, opaque, unguessable external ID for each user (see below).
1. Identify from your backend (POST /v1/identify)
Section titled “1. Identify from your backend (POST /v1/identify)”Call this once when a user logs in. Read the visitor’s pathbound_visitor_id cookie (it’s intentionally JS-readable, so your frontend can forward it or your backend can read it from the request) and assert the binding:
curl -X POST https://api.pathbound.ai/v1/identify \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_id": "usr_8f3a2c9e-…", "anonymous_id": "the-pathbound_visitor_id-cookie-value", "traits": { "email": "[email protected]", "firstname": "Ada", "lastname": "Lovelace", "company": "Example Inc" } }'// e.g. inside your login route handlerawait fetch('https://api.pathbound.ai/v1/identify', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PATHBOUND_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ external_id: user.pathboundExternalId, // stable + opaque anonymous_id: req.cookies.pathbound_visitor_id, // stitches prior anonymous events traits: { email: user.email, firstname: user.firstName, lastname: user.lastName, company: user.company, }, }),});One call does all of this:
- Resolves or creates the contact — by
external_id, thenemail,linkedin_url,phone. A user who isn’t in any connected CRM yet gets a unified profile immediately, instead of staying anonymous until a sync lands. - Binds the external ID and marks the contact identified.
- Merges the traits most-recent-wins — your values are never silently clobbered by a later integration sync.
- Binds the visitor under the one-device-one-contact rule.
- Back-fills the visitor’s prior anonymous events onto the contact.
Response
Section titled “Response”{ "status": "success", "contact_id": "…", "created": true, "events_linked": 42, "timestamp": "2026-06-09T12:00:00.000Z"}Request fields
Section titled “Request fields”| Field | Type | Required | Notes |
|---|---|---|---|
external_id | string (≤100 chars) | yes | Your stable, opaque id for the user. Becomes the contact’s external_contact_id — the join key shared with the cookie and your CRM. |
anonymous_id | string | no | The visitor’s pathbound_visitor_id cookie. Stitches their prior anonymous events onto the contact. Omit it and you still upsert the contact, just without back-fill. |
traits | object | no | Contact properties to set/update (email, firstname, lastname, company, linkedin_url, phone, …). Merged most-recent-wins. |
2. Set the cookie at login (continuous attribution)
Section titled “2. Set the cookie at login (continuous attribution)”The cookie makes every subsequent event resolve to the contact at ingest, instead of waiting on the periodic enrichment pass. Set it client-side when the user authenticates, using the same opaque ID you pass to /v1/identify:
document.cookie = `pathbound_external_contact_id=${user.pathboundExternalId}` + `; path=/; max-age=31536000; SameSite=Strict; Secure`;On logout, clear it so the next person on that browser isn’t attributed to the user who just left:
document.cookie = 'pathbound_external_contact_id=; path=/; max-age=0';The cookie value and the
external_idyou pass to/v1/identifymust be identical — that single opaque ID is what ties the two paths (and your CRM) to one contact.
3. Forms (no extra work)
Section titled “3. Forms (no extra work)”If you use Pathbound form capture, a submission automatically links its visitor_id to a contact by email — no identify call needed for that path. It obeys the same one-device-one-contact rule.
4. If you also sync a CRM
Section titled “4. If you also sync a CRM”Set the same opaque ID in your CRM’s Pathbound external-ID field (for HubSpot, the sb_external_contact_id custom property). Then a contact synced from your CRM and the same person identified on your website converge on one external_contact_id — one unified profile instead of two.
Rules worth knowing
Section titled “Rules worth knowing”Choose a safe external ID
Section titled “Choose a safe external ID”The external_id is a join key, not a secret — but make it opaque and unguessable (a UUID or random token). Never use a sequential integer or an email address: a logged-in user can edit their own cookie, and a guessable ID would let 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 — an unguessable ID closes the write-side (timeline-poisoning) gap entirely. The authenticated /v1/identify path is immune regardless.
One device → one contact
Section titled “One device → one contact”A pathbound_visitor_id (a browser/device) can belong to at most one contact. On a genuinely shared browser, a second user’s identify still creates or updates their contact, but the visitor binding to the first owner is refused (and logged) rather than forking that device’s history across both profiles. Moving a visitor between contacts is a deliberate operation, not an ingestion side effect.
Timing
Section titled “Timing”/v1/identify and the cookie attribute in real time. Events captured after login without the cookie set (relying only on the visitor↔contact link from a one-time identify) attach on the periodic enrichment pass rather than instantly — which is the main reason to set the cookie too.
See also
Section titled “See also”- Identity & fingerprinting — how visitor IDs are assigned and recovered across cookie loss.
- Install the snippet — the anonymous tracking layer this builds on.
- Events API — read the unified timeline (
identified_onlyfilters by whether an event is attributed to a contact).