Identity Resolution: Stitching Users Without Corrupting Data

Identity resolution is the practice of linking a user's anonymous pre-signup activity to their known profile once they identify themselves. The mechanism is almost boringly consistent across tools: when a single event carries both a device/anonymous ID and a user ID, the two get connected, and the anonymous history gets attributed to the known human. Done right, you get one continuous record instead of a stranger and a stranger's twin. Memorize one fork before anything else: the merge happens either at write-time, where events land already resolved, or at read-time, where they're stitched later when you run the query.

What identity resolution actually is

What are we stitching together? Two identifiers. The first is the anonymous ID, sometimes called a device ID, assigned automatically the moment a browser or app phones home, before anyone tells you who they are. The second is the user ID, which shows up only after a person signs up, logs in, or otherwise identifies themselves. Identity resolution ties those two threads into one.

Here's the part that surprises people. Most of this stitching is deterministic, not probabilistic. Nobody is guessing that two sessions "probably" belong to the same person based on IP and screen resolution. A specific event links the two identifiers, and that event triggers the merge. Snowplow describes its own approach this way, writing in an April 2026 post that "every merge is triggered by a specific event linking two identifiers. Every merge is auditable." Mixpanel frames the same trigger mechanic — one event containing both IDs kicks off the link. That determinism is a feature. It means you can trace why two profiles became one, which matters enormously the day a merge goes wrong.

The pitfall that started this article: the orphaned anonymous user

Let me talk about the failure that sends confused PMs into my inbox. Someone browses your marketing pages anonymously for three days, signs up on day four, and your dashboard cheerfully reports two new users that week: the ghost who browsed and the account who registered. The same human, counted twice, one of them a phantom.

Why does this happen when the tools supposedly merge everything? Because the merge is retroactive, and retroactive merging depends entirely on timing and query logic. Amplitude's own documentation notes that it "merges a user's event data on the backend, which connects the correct user ID to any anonymous events the user generated before the user ID assignment." Sounds airtight. But in an Amplitude Community thread, their support team clarified the catch: the merge works cleanly when "the anonymous user logs into their existing account within the same session," after which subsequent events map to the existing ID. Fall outside that tidy window — log in the next day, on a different device — and your counting logic can still bucket the anonymous chapter as a brand-new person.

Now, the "new users" number. It's the metric everyone screenshots for the board and nobody audits. A "new users" line that quietly absorbs your own un-merged ghosts isn't measuring growth. It's measuring your reconciliation bugs. If you've never checked how merge timing feeds that count, you don't have a growth number, you have a hopeful one. (For more on that species of metric, see our two-question test for vanity metrics.)

The three moments where stitching happens

Before signup: the anonymous ID

Think of the anonymous ID as tapping into the metro with a single-use paper ticket. The turnstile records the tap, the ticket accumulates a little history for this one trip, but the transit system has no idea who you are. Every anonymous event — page views, feature clicks, that abandoned trial — attaches to this throwaway credential. Real history, just orphaned from a name.

At identify: the merge/alias step

Then you buy the registered travel card. The moment you tap it against the reader while the system can still see your old paper ticket, the two get linked, and your prior trips fold into your named account. In analytics terms, that's the identify call: a single event carrying both the anonymous ID and the user ID. Mixpanel's Simplified ID Merge, per its 2026 community documentation, is "triggered by a single event that contains both $device_id and $user_id," after which "all past and future events associated with either ID are then attributed to the same user." Past and future, from one event. That's the pivot the whole practice turns on.

After the fact: read-time reconciliation

The third moment is quieter and often invisible: reconciliation that happens at query time rather than being baked into the stored events. Your raw event data still shows two separate IDs, but a mapping table resolves them into one person the instant you ask a question. Nothing was rewritten on ingest. The stitch lives in the read path. Which brings us to the real architectural decision.

Merge-on-write vs. merge-on-read: the architectural fork

This is the spine of the whole thing. Do you prep every ingredient before service, or plate to order?

Merge-on-write is prep-ahead cooking. Events land in your warehouse already resolved to one identity. Queries run fast because the hard work is done, but you've committed to a decision at ingest time, and undoing it means reprocessing. Merge-on-read is plating to order. Raw events stay untouched, and a mapping table stitches them each time someone runs a query. Snowplow lays out exactly this contrast, noting in an April 2026 post that "most identity resolution happens after the fact. Data lands in your warehouse, a batch process runs, and profiles get stitched together hours or days after the events happened." They offer real-time pipeline stitching as the write-time alternative, where events arrive resolved.

The tradeoffs cluster around three things: how fast you can read, how easily you can audit or reprocess, and how badly a mistake corrupts stored data. Here's how I'd lay it out.

Decision table

Merge-on-write Merge-on-read
Query latency Low — events pre-resolved Higher — stitch runs per query
Auditability Merge baked in; harder to see the "before" Raw events preserved; easy to inspect what merged
Reprocessing cost High — fixing a bad merge means rewriting stored events Low — rebuild the mapping table, raw data untouched
"New user" miscount risk Lower once resolved, if timing is correct at ingest Higher if query logic doesn't apply the mapping
Best-fit use case Latency-sensitive activation flows, real-time dashboards Exploratory analysis, frequent re-definition of identity rules

Neither wins outright, and that's the uncomfortable truth. Merge-on-write gives you speed and a clean-looking table, but a wrong merge is now cooked into your data and expensive to reverse. Merge-on-read keeps your raw events honest and your mistakes cheap to fix, at the cost of doing that work every single query.

Where it breaks: edge cases every implementation hits

Shared devices, logout, and reset

Deterministic stitching assumes one device roughly maps to one human. The family tablet laughs at this. So does the library computer and the trade-show kiosk. Snowplow calls these out plainly, noting that "shared devices (family tablets, public computers, kiosks) are where deterministic stitching can misattribute events." Person A logs in, person B logs in on the same device an hour later, and now their histories are threatening to fuse into one confused mega-profile.

The mitigations are unglamorous and effective. Mark certain identifiers as unique so they can't spread across users. Set identifier priority carefully. Or flag known shared-device IDs and exclude them from merges entirely. Logout and reset behavior deserve the same scrutiny — if logging out doesn't sever the anonymous thread, the next user inherits it.

Cluster caps and orphaned IDs

Some tools cap how many identifiers can belong to one person. Mixpanel's Original ID Merge, per its 2026 migration docs, limits "each user's ID cluster to a maximum of 500 Distinct IDs." Hit that ceiling and any new ID "can no longer be merged into the same ID cluster. They will then become orphaned (duplicate users on Mixpanel)." So a heavy user across many devices and sessions eventually spawns duplicates — the exact miscount we're trying to prevent. Mixpanel's Simplified ID Merge lifts that cap, but with a rule of its own: it forbids merging two user IDs together. You trade one constraint for another, which is honestly the theme of this entire topic.

Identifier priority as the main safety lever

If there's one dial that prevents most bad merges, it's identifier priority. Segment ranks identifiers to decide which ones drive a merge, and its June 2026 settings documentation states that "by default, Segment explicitly orders user_id and email as rank 1 and 2, respectively. All other identifiers are in alphabetical order beginning from rank 3." Their guidance is to give immutable IDs the highest priority, so that a stable, never-recycled identifier decides who gets stitched to whom. Get the ranking right and shared-device chaos becomes manageable. Get it wrong and a low-quality identifier starts fusing strangers.

Worked example: counting new users with and without stitching

Numbers make this concrete, so here's a small constructed sequence for one real human, Priya. Five events, illustrative, not from any vendor.

  1. Mon 09:00 — anonymous ID a1, views pricing page (web)
  2. Mon 09:04 — anonymous ID a1, starts a trial signup form
  3. Mon 09:06 — anonymous ID a1 + user ID u_priya, identify fires on account creation
  4. Tue 18:30 — anonymous ID a2 + user ID u_priya, logs in on her phone
  5. Wed 08:15 — anonymous ID a2, opens a feature (phone, already logged in)

Count new users naively, keying on whatever ID each event carries, and you can get two or even three. Anonymous a1 looks like one new user on Monday. Anonymous a2 on her phone looks like another new user on Tuesday, because your query never applied the mapping. The identify events sit there holding the answer, and nobody read it.

Now resolve properly. Event 3 links a1 to u_priya. Event 4 links a2 to u_priya. Every one of these five events belongs to a single person who became new exactly once, on Monday. Resolved count: 1. That gap between three and one is not rounding error. Across thousands of users it's the difference between a growth chart and a fanfic.

How the major tools approach it

A quick, even-handed survey. Same rubric each time: what triggers the merge, whether it leans write or read, one notable edge case, and a maturity caveat.

Amplitude merges on the backend, connecting the user ID to earlier anonymous events on the same device. It leans write-time and retroactive. The notable edge case is the same-session assumption behind clean merges; step outside it and miscounts creep in. Caveat: check how your instance handles cross-session, cross-device logins before trusting new-user counts.

Mixpanel triggers merges from a single event carrying both IDs. The headline decision is Original vs. Simplified ID Merge — the 500-ID cap and orphaning under Original, versus the lifted cap but no two-user-ID merges under Simplified. Caveat: which mode you're on materially changes your duplicate rate, so confirm it.

Snowplow is the clearest illustration of the fork, offering real-time pipeline stitching against a batch dbt mapping table, with merges that are deterministic and auditable. Its edge-case honesty around shared devices is a model for how vendors should document limits. Caveat: it assumes you have the data-engineering muscle to run either path.

Segment makes identifier ranking the control surface, ordering user_id and email first by default and recommending immutable IDs at the top. It's less about a single merge moment and more about the rules that govern all of them. Caveat: default rankings are a starting point, not a finished config.

Kixo is an AI-native product analytics platform with iOS, Android, and Web SDKs covering funnels, retention, cohorts, and user flows — all of which need a resolved identity to hold a person's path together. Its distinguishing angle is chat-first querying: you ask a question in plain language and get charts back with a visible reasoning trail, a different read-time interaction model than the usual query builder. I'll be honest about the limits of what I can say. Its published material doesn't specify the underlying stitching architecture, so I'd file it as one of several platforms treating a resolved path as table stakes, not as a claim about a particular merge mechanism.

The mistakes everyone makes

  • Trusting "new users" without checking merge timing. If you can't explain how a same-day-vs-next-day login affects that count, you don't yet know what the number means.
  • Ignoring shared-device misattribution. Family tablets and kiosks quietly fuse strangers. Set uniqueness and priority before they do.
  • Choosing merge-on-read for a latency-sensitive activation flow. Plating to order is lovely until your onboarding trigger needs the resolved identity now.
  • Never auditing which identifier won a merge. When two profiles fuse incorrectly, the only way back is knowing which identifier drove it, which is why auditability isn't a nice-to-have.

The through-line is the one I keep repeating to reformed dashboard hoarders like my past self: a metric you haven't audited is a rumor with a chart attached. Identity resolution is where a lot of those rumors are born.

Quick reference: choosing your approach

If activation flows or real-time dashboards depend on knowing who someone is, lean merge-on-write and accept the reprocessing cost. If you're still refining what "the same user" even means, or you value cheap corrections and preserved raw events, lean merge-on-read. Whichever you pick, set identifier priority so an immutable ID wins, and mark shared-device identifiers so they don't spread history across humans. Priority and uniqueness are the levers that stop bad merges before they corrupt anything.

Before you tune any of this, make sure your upstream instrumentation is solid — a clean tracking plan and sane sessionization rules prevent half the identity messes I've seen. And if your resolved numbers ever look too good, revisit Simpson's Paradox before you celebrate.

FAQs

Why is my anonymous user still counted as a new user after they signed up? Because merging is retroactive and timing-dependent. If the login happens outside the window your tool merges cleanly in, or your query never applies the identity mapping, the anonymous chapter can still register as a separate new user. Check the merge timing before trusting the count.

Is identity resolution deterministic or probabilistic? In the major analytics tools, it's mostly deterministic: a specific event carrying two identifiers triggers the link. That's what makes merges auditable, as Snowplow emphasizes. Probabilistic stitching exists elsewhere but isn't the default here.

What's the difference between merge-on-write and merge-on-read? Merge-on-write resolves identity at ingest, so stored events are already stitched — fast to query, expensive to fix. Merge-on-read keeps raw events untouched and stitches them at query time — cheaper to correct, slower to read.

How do I stop a shared family tablet from merging two people? Give an immutable, per-user identifier the highest priority, mark identifiers as unique where appropriate, and exclude known shared-device IDs from merges. Segment's ranking model and Snowplow's uniqueness settings are both built for exactly this.