Skip to content
PathboundDOCS

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 isTrustWhen it resolves
POST /v1/identifyAuthenticated server call at loginYour API key asserts the identityImmediately (creates the contact + back-fills)
pathbound_external_contact_id cookieA cookie your app sets at loginSoft (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.

  • The tracker snippet is installed and your domain is verified.
  • You have an API key with the contacts:write scope (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:

Terminal window
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 handler
await 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:

  1. Resolves or creates the contact — by external_id, then email, 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.
  2. Binds the external ID and marks the contact identified.
  3. Merges the traits most-recent-wins — your values are never silently clobbered by a later integration sync.
  4. Binds the visitor under the one-device-one-contact rule.
  5. Back-fills the visitor’s prior anonymous events onto the contact.
{
"status": "success",
"contact_id": "…",
"created": true,
"events_linked": 42,
"timestamp": "2026-06-09T12:00:00.000Z"
}
FieldTypeRequiredNotes
external_idstring (≤100 chars)yesYour stable, opaque id for the user. Becomes the contact’s external_contact_id — the join key shared with the cookie and your CRM.
anonymous_idstringnoThe 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.
traitsobjectnoContact properties to set/update (email, firstname, lastname, company, linkedin_url, phone, …). Merged most-recent-wins.
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_id you pass to /v1/identify must be identical — that single opaque ID is what ties the two paths (and your CRM) to one contact.

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.

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.

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.

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.

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

  • 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_only filters by whether an event is attributed to a contact).