Identity resolution

Every event names a subject in its identity block. Customer 360 resolves that subject into exactly one customer, running deterministic graph rules, and then appends the event. Understanding these rules is what lets a producer send the right identity and a consumer read the right owner.


One customer per event

At ingest, the resolver picks a primary node from the identity block using a fixed precedence: the first present of userId, then phone, then email, then sessionId, then zbIdSubject alone. That node either matches an existing customer or bootstraps a new one, and the event is inserted onto the resolved customer.

Because userId is the strongest per-platform key, always send it when you have it. Because zbIdSubject is the cross-channel canonical key, send it too, even alongside a userId: that is what unifies a customer across channels.


The ZB ID subject auto-merges

The zbIdSubject (the ZB ID JWT sub UUID) is the authoritative identity, and the only field that triggers an automatic merge at ingest:

  • If the event carries a zbIdSubject that already belongs to another customer, and the current customer has no ZB ID of its own, the two are auto-merged onto the ZB ID survivor.
  • If the current customer has no ZB ID, the subject is attached to it and the customer is marked resolved.
  • If the current customer already has a different ZB ID, the subject is not force-merged; a conflict is recorded for review and the event stays on the current customer.

This is why sending the ZB ID subject on every event you can is the single most valuable thing a producer does: it is the key that collapses a guest, a mall account, and a loan applicant into one customer.

Send the ZB ID subject when you have it

{
  "identity": {
    "platform": "smilemall",
    "userId": "42",
    "zbIdSubject": "b1e5c8a0-1111-2222-3333-444455556666"
  }
}

Phone and email never auto-merge

A phone or email that already appears as an identity on a different customer never triggers an automatic merge. Instead the resolver records a soft-match candidate and keeps the two customers separate until the match is confirmed out of band. Phone and email are shared, reused, and mistyped too often to be safe auto-merge keys.

So a producer that only knows a customer by phone should still send it (it strengthens the graph and creates the soft link), but should not assume two records with the same phone are already one customer. A nationalId behaves the same way: it strengthens resolution but is not an auto-merge key on its own.


identity.claimed merges

The explicit way to merge is the identity.claimed event. Send it with the registered user's userId (or zbIdSubject) in the identity block, and the identity being absorbed in the payload as claimedType and claimedValue. The customer that owns the claimed identity is merged into this event's customer.

This is the standard flow for a registered user absorbing a prior guest: the guest browsed and checked out under a sessionId or a bare phone, then signs in, and your app emits identity.claimed to fold the guest's history onto the account.

identity.claimed

{
  "eventId": "mall-claim-user42-phone",
  "type": "identity.claimed",
  "occurredAt": "2026-08-10T09:20:00.000Z",
  "identity": { "platform": "smilemall", "userId": "42" },
  "payload": {
    "claimedType": "phone",
    "claimedValue": "+263771234567"
  }
}

What consumers must handle

Because merges happen after the fact, a consumer of the feed must treat customerId as mutable:

  • Key on eventId, not customerId. A late merge repoints historical events' customerId onto the survivor. The id and eventId are stable; the customerId can change between two reads of the same event.
  • Re-resolve the current owner when you need it. If you need the customer as of now, look them up with a profile read, which always follows the merge chain to the surviving customer.
  • Expect at-least-once delivery. Make your processing idempotent so a replay after a restart or a producer repair pass is a safe no-op.

Was this page helpful?