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.
Traits (spend totals, counts, recency) are computed on read, not stored per event, and always on the fee-free money basis the producers sent. The service computes no fees; it only sums what you stored.
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
zbIdSubjectthat 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.
A phone or email seen for the first time may attach to the current customer as a satellite. A phone or email that already identifies a different customer raises a soft-match candidate, not a merge.
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, notcustomerId. A late merge repoints historical events'customerIdonto the survivor. TheidandeventIdare stable; thecustomerIdcan 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.
To read a unified customer, and its identities, consents, and computed traits, use the Profiles endpoints with the profile scope. Every profile lookup follows the merge chain to the surviving customer.