Crossdeck Docs
Dashboard

Identity Explorer

Workbench 7 min read · One person, one canonical customer — proven, not assumed

A single person arrives at your product as many fragments: an anonymous device id minted before they ever sign in, the user id you assign when they do, a Stripe or Apple customer the moment they pay. Crossdeck’s job is to collapse those fragments into one canonical customer so you count one person once. The Identity Explorer is how you see that collapse — and how you test it. It does not hold its own opinion of who someone is; it reuses the exact resolver every event runs through and round-trips every fragment back through it. A fragment that doesn’t resolve home is a split, shown mechanically.

TL;DR

The fragments of a person

Crossdeck stores one person as a single canonical customer record (a cdcust_…) that absorbs every identity fragment it has seen for them. Those fragments are the axes the Identity Explorer walks:

AxisWhere it comes from
Anonymous deviceThe id the SDK mints before sign-in. A person can carry several over time; they all live on the one customer.
Developer user idThe id you pass to identify() when the person signs in.
Stripe customerThe cus_… attached when they pay through Stripe.
Apple transactionThe original transaction id from an App Store purchase.
Apple appAccountTokenThe install-stable token the Swift SDK stamps on a purchase — its own identity channel, independent of who owns checkout.
Google purchaseThe purchase token from a Play Store purchase.

Each axis also has an index entry — a small pointer (anon:…, developer:…, stripe:…) that maps that fragment back to its canonical customer. The index is what makes resolution O(1) on every event. The Identity Explorer’s test is, in effect, “does every pointer still point home?”

The round-trip test

This is the heart of the tool, and why it is trustworthy. For each axis the canonical person holds, the Explorer feeds that axis back into resolveCrossdeckCustomerId — the identical function the event pipeline calls to decide “whose event is this?” — and follows the merge chain to the live customer. The answer is compared to the canonical person.

StateMeaning
linkedThe axis resolves home to this canonical customer. The stitch holds.
driftedThe axis resolves, but to a different customer — its index pointer points away from this person. A drift the reconciler should re-home.
unlinkedThe axis has no index entry at all — an orphan fragment. This is how “two dots for one person” appears at the index layer.

A person is consistent when every axis is linked. Because the test runs the production resolver rather than re-implementing the logic, a green verdict here means the same thing a green verdict means in production: every fragment of this person funnels to one customer, so they count once and see one coherent history.

Why round-trip instead of just reading the record?

Reading the customer doc tells you what Crossdeck believes. Round-tripping each axis tests whether the index agrees — whether a later event arriving with that fragment would actually land on this person. The two can disagree after an interrupted merge or a drifted pointer, and that disagreement is exactly the bug you came to find.

Running a lookup

Open Developers → Workbench → Identity Explorer. Pick the identifier type (or leave it on Auto-detect), paste the value, and run it:

A value that resolves to nothing returns a plain Not found — a fact, not an error. An anonymous id that resolves to nothing is itself the finding: an unstitched fragment with no customer behind it.

Reading the result

The result reads top to bottom:

The enforced contract

Crossdeck runs an enforced contract test, identity-stitch, that re-derives every customer merge from source records and checks six invariants — the survivor is identified, every loser points to the survivor, no subscription or purchase is left orphaned, revenue is never double-counted, and the survivor’s entitlements cover its subscriptions. The Identity Explorer surfaces that contract’s most recent recorded verdict for your project, so the per-person view sits next to the project-wide guarantee.

It reads the verdict; it does not run the contract.

Running the contract writes a run record — a mutation. To stay strictly read-only, the Explorer reads the latest verdict already on file rather than triggering a fresh evaluation. The verdict you see is the platform’s own last word, unedited.

Where “two dots” come from

The most common split has a simple cause, and the Explorer makes it legible. On mobile, the Swift SDK mints one anonymous id at first launch and keeps it for the life of the install — one device, one id. On the web, the anonymous id lives in per-origin storage, so a person who onboards on your marketing origin and lands in the app on a different origin re-mints a fresh id. Until an identify() stitches the two, that one person exists as two anonymous fragments — “two dots.” In the Explorer, the second fragment shows as unlinked (no customer yet) or, once stitched, as a second linked anonymous axis on the same canonical customer.

The fix is identify, not the Explorer.

The Explorer reveals a split; it never repairs one (it is read-only). The repair is the normal stitch path: when the person signs in and the SDK calls identify(), Crossdeck attaches the new fragment to the existing customer. See Identify users.

Read-only