> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Identity resolution and merge semantics

> How an incoming CDP event resolves to one contact: identity keys and normalization, deterministic rules versus the probabilistic review band, merge survivorship and provenance, downstream effects on segments and predictive routing, and where duplicates surface.

# 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](/guides/identity-resolution); the shape of the
upstream stream is
[The CDP event model](/concepts/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:

| Key            | Normalized to                           | Carried by                                             |
| -------------- | --------------------------------------- | ------------------------------------------------------ |
| `external_id`  | Your system's stable key, taken as sent | Contact creation, imports, `identify` (`userId`)       |
| Email          | Lowercased and trimmed                  | `identify` traits, imports, `POST /api/v1/contacts`    |
| Phone          | E.164 digits (`+14155551234`)           | `identify`, messaging channels, inbound voice, imports |
| `anonymous_id` | The SDK-minted id, verbatim             | Web and mobile events before `identify`                |

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](/concepts/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](/concepts/cdp-segment-recompute-model).
* **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 queue** — `GET /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 surface** — `GET /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](/guides/identity-resolution)) walks the surfaces
above; this page defines what each of them guarantees.

## Cross-references

* [Identity resolution (guide)](/guides/identity-resolution) — the
  operational queue, console, and undo flow.
* [The CDP event model](/concepts/cdp-event-model) — the identity-hint
  envelope upstream of resolution.
* [How CDP segments recompute](/concepts/cdp-segment-recompute-model) —
  the membership cadence a merge feeds.
* [CDP API reference](/api-reference/endpoints/cdp) — the endpoint-level
  surface of every call named above.
