Skip to main content

Identity resolution and merge semantics

Every segment membership, funnel, attribution model, and routing decision is only as correct as the contact row it reads. This page defines how an incoming identifier becomes one contact — which keys resolve, which merge direction wins, what each fold-in writes onto the survivor, and what updates downstream — so you can reason about the duplicate or split-contact shapes the operator surfaces show. The operational guide (queue, console, undo window) is Identity resolution; the shape of the upstream stream is The CDP event model.

Section 1 — Identity keys and precedence

Four keys anchor a contact. Each is normalized before it enters the identity index, so formatting never decides a match: Precedence is tenant-owned. PUT /api/v1/cdp/identity-rules authors one rule per identifier type (external_id, email, phone, anonymous_id) with a numeric priority; a lookup matches on the strongest enabled rule at the time it runs. An operator who imports from a CRM typically puts external_id first so the CRM key, not a respelled phone, settles the match. The rule set resolves lookups everywhere — ingest binding, imports, inbound messaging, and the stitch API. Anonymous traffic is a first-class key, not noise. Events carry one or both of userId and anonymousId; they hold contact_id = NULL until the identity pass binds them (see The CDP event model, Section 2). When an identify later pairs that anonymous_id with a strong phone or email, the pre-identify session folds onto the resolved contact in the same request.

Section 2 — Deterministic versus probabilistic resolution

Orbit runs two resolution layers on the same identifier sets, and the distinction decides what a duplicate scan can do unattended:
  • Deterministic. Rows whose normalized identifiers are equal on a declared key group transitively into one profile — the email plus phone pair this-and-that corroborate each other, so equality, not similarity, does the match. POST /api/v1/cdp/identity-resolution/preview dry-runs the groups without writing; POST /api/v1/cdp/identity-resolution/run auto-merges each group whose confidence clears your threshold and leaves the rest as a review queue.
  • Probabilistic. The remainder — a fuzzy name, a phone-format respelling, a shared non-unique channel id (WhatsApp or Viber) — ranks into a review band instead of merging. GET /api/v1/cdp/identity/merge-candidates returns ranked pairs with a confidence and a band; min_confidence and include_auto_merge=false bound the scan to the review queue. A phone-only link whose members carry different emails (the shared-handset or reassigned-number ambiguity) lands here by design — it is a steward’s call, never an automatic fold.
Set the auto-merge threshold deliberately: the run pass applies only the groups at or above it, and a dismissed pair re-enters the queue only if the underlying data changes. When you change a rule, the simulator (POST /api/v1/cdp/identity-resolution/simulate) previews which pairs the new rule would collapse and raises an over-merge warning — run it before enabling so a too-loose rule fails in preview, not on live contacts.

Section 3 — Merge direction and survivorship

The primary record survives; secondaries fold in and are marked merged-away so channels cannot re-contact them as distinct people. Field-level survivorship is decided by three sources, in a strict precedence:
  1. Per-request pins (fieldStrategies, plus attribute.<key> pins for custom fields) — set on the merge call itself, highest precedence.
  2. Your tenant survivorship policy (GET/PUT/DELETE /api/v1/cdp/survivorship-policy) — a rule per governed field over the scalar contact columns (phone, email, WhatsApp id, Viber id, name, company, country code, timezone, language, external id). Strategies are prefer_target / prefer_source, most_recently_updated / most_recently_created, prefer_non_null, and prefer_source_system (value from the record whose source is a named system — crm, salesforce, import).
  3. The safe default — blanks and nulls never overwrite a populated value; a partial update cannot erase an established trait.
Two properties make the fold auditable rather than mysterious:
  • Reversal is bounded, not vague. Every merge returns a merge_id; the undo window stays open for 30 minutes, and POST /api/v1/contacts/unmerge/:mergeId restores the split while it is open. GET /api/v1/contacts/merge-history is the full audit list.
  • Every merge writes a durable audit row recording the verdict, the confidence, and the identifiers that corroborated the match — the GDPR art. 15/22 “how did this decision happen” answer. A merge you applied through the review queue carries the steward’s verdict on the same row.
Provenance follows the survivor: the source-of-truth ordering you set in the policy decides which record’s value a given field reads, and the audit trail records which fold contributed it. A merged-to contact is a synthesis with a paper trail, not a guess.

Section 4 — Downstream effects

A merge redirects channel membership, tags, custom-field values, conversation history, and CDP events onto the survivor — so consumers that read the contact row re-derive from the unified profile on their own cadence:
  • Segments. The same fold-in that redirects events also redirects the contact’s segmentable fields; auto-refresh segments pick the change up on their next evaluation cadence, the scheduled refresh converges the rest. See How CDP segments recompute.
  • Predictive attribute routing. Inbound voice routing matches on the resolved contact’s attributes (score and trait tags). After a merge, a caller’s routing verdict derives from the survivor’s profile — a routing rule that once split one person across “high value in one record, no score in the other” now reads one profile consistently.
  • Conversation threading. Inbound messaging, voice, and video resolve by the bound phone or email; they read the survivor directly, so two channels that once opened parallel threads converge onto one timeline.
  • Computed traits and scores. Churn, CLV, and engagement scores recompute over the unified event history instead of splitting across two partial records.
Conversely, a split (a duplicate that has not merged) propagates the same way: membership counts it twice, routing reads two halves, and the conversation timeline shows parallel threads. That is the failure shape the operator surfaces below are for.

Section 5 — Where duplicates surface

Duplicates announce themselves on three read paths; check them when a count looks inflated or a caller history reads wrong:
  • The review queueGET /api/v1/cdp/identity/merge-candidates or the dashboard Audience → Identity resolution page ranks open pairs; duplicates that never reach a merge sit here.
  • The contacts surfaceGET /api/v1/contacts (or the merge-history feed) shows the survivor and its folded-in secondaries; a contact with an unexpected twin usually cleared the review queue or came back through a re-import.
  • Number-ownership conflicts — when the same phone claims two contact rows (common after a re-import that bypassed external_id), inbound resolution and screen-pop read an ambiguous owner until a merge settles it.
The fix is the same whichever surface you read it on: run the deterministic preview, take or dismiss the pair on the review queue, and let the 30-minute unmerge catch the rare over-merge.

Section 6 — Tenant-owned decider

Every behavior above is a tenant-controlled surface; the platform enforces no canonical merge policy:
  • Rule priority and identity keys are set on identity-rules.
  • The auto-merge threshold and the review band are set per tenant.
  • Survivorship precedence (pins → policy → blank-safe default) is set per tenant and per request.
  • Merges are reversible inside a 30-minute window and audited permanently, so an incorrect fold is recoverable, not silent.
Nothing here is a platform-mandated behavior claim: Orbit supplies the match contract and the audit trail; you decide what merges. The guide (Identity resolution) walks the surfaces above; this page defines what each of them guarantees.

Cross-references