Skip to main content

Stitch anonymous sessions into known contacts

Anonymous visits are a fact every tenant builds around: a visitor reads the site before they ever log in, a caller reaches you before their number resolves to a profile, a video-lobby guest joins by link. Until an identifier binds them to a person, each of those sessions sits on its own contact row and the story splits. Orbit has two stitch seams — pick the one that matches WHEN you learn who the visitor is:

1. Real-time bind — POST /sdk/identify

The moment your site learns who the visitor is, bind the strong identifier on the anonymous session:
One call does four things:
  • Web tracking backfill — every pre-identify browser event captured under the same anonymous_id is backfilled onto the resolved contact, so the contact-360 timeline reads the full funnel (not “first seen after login”).
  • Messaging — inbound messaging replies (SMS, WhatsApp, and email) resolve by the phone or email you just bound; the channel-carried identifier does the binding from there.
  • Voice — inbound callers resolve by the same phone number the identify call bound, so a returning call lands on the same contact the browser session folded into.
  • Video — video joins stamped on the SDK-identified contact attribute to them; pre-identify joins stay anonymous-scoped until the visitor identifies.
Pass phone or email (prefer your own stable identifier where possible; at least one of the two is required). Re-calling with the same identifier and the same anonymous_id is a no-op once the contact exists, so SDKs can retry safely. Every identify call also lands a chain entry in your audit feed naming the bound identifier and each surface the stitch unlocked — a compliance “which channels did this identify enable?” trace is one query away. See the Embedded SDK API reference for the full envelope, and the web SDK if the site calls this from its own library rather than raw fetch.

2. Late-declared operator stitch — POST /api/v1/cdp/identity-stitch

The automatic bind above only fires when the web SDK can name the known identifier in-session. It never guesses at a merge the deterministic rules would reject, and it won’t fold two live contact rows you only later discover are the same person — a widget visitor bound after an inbound voice call created their primary, or a video-lobby guest leg that only joins by link. That late-discovered link is exactly what the server-key stitch endpoint declares:
The endpoint resolves both ids against your live contact graph and collapses them into one record in a single durable transaction — messages, conversations, agent conversations, CDP events, video participants, and consent all fold onto the survivor, the same cascade the merge console uses. The response reports exactly what happened: Two guardrails make this safe to run from an operator console:
  • Shape gate. A known_id that looks like a phone number or email address is refused, because phone/email must flow through identify with an explicit trait key — never fold a contact here.
  • Audited. Every call writes one consolidated cdp.identity_stitched audit entry with the pair and the outcome, so the fold-in is replayable from your operator console. Replies stay 200-class even on the refused and error outcomes, so a console can tone the row off one field.
Requires a server key with the contacts:write scope and the owner, admin, or developer role — collapsing identities is a tenant-wide attribution decision. A stitch is one of the three canonical anonymous → known transitions in Orbit:
  • In-session bind (/sdk/identify, /cdp/identify) — you ship the anonymous and the known identifier together.
  • Pairwise collapse (/cdp/alias) — your ingest key declares that one id folds into another; stages 2 and 3 of the canonical CDP call.
  • Late-declared operator stitch (/cdp/identity-stitch) — an authenticated surface resolves both ids against live contacts when the first two missed.
After the fold-in, the survivor follows the same rules as any merged record: the contact’s survivorship policy decides which scalar fields live on the last write, and the merged record’s consent verdicts (checked via GET /api/v1/cdp/consent/:contactId) govern whether each folded-in channel may still be contacted. Run one consent check if the two records disagreed on opt-in/opt-out before you stitched.

See also