# Identity resolution - Docs

## TL;DR

-   [Identity resolution is your engineering problem](#identity-resolution-is-your-engineering-problem) – PostHog consumes what you give it. If your identity data is incoherent, every downstream feature inherits that incoherence.
-   [Think in layers](#the-identity-layer-model) – stable IDs, authentication IDs, and device IDs serve different purposes. Know which layer you're operating at.
-   [The golden path: assign a stable ID early and never change it](#choosing-your-identity-strategy) – mint it in one place, pass it to every environment. This eliminates an entire class of problems across flags, experiments, session replay, and analytics.
-   [Pass the ID across transitions explicitly](#golden-path-stable-id-from-first-touch) – web to mobile, marketing site to product, client to server. If two environments generate IDs independently, they will diverge.
-   [Link IDs explicitly](#linking-ids) – PostHog can merge what you tell it to merge. It can't infer that two IDs belong to the same person.
-   [Verify your implementation](#how-to-verify-identity-is-correct) – check person profiles, not assumptions.

 | --- | --- |
| Feature flags | Different values before and after login | [Hash input changed](/docs/feature-flags/production-ready.md#resolve-identity-before-evaluating-flags) |
| Experiments | User in both control and test | Two unlinked persons, each assigned independently |
| Experiments | Exposure exists but conversion missing | Exposure on anonymous ID, conversion on identified ID |
| Session replay | Recording breaks at login | distinct_id changed mid-session without linkage |
| Funnels | Conversion attributed to wrong user | Events split across unlinked persons |
| Error tracking | Phantom users with one error each | Transient IDs creating a new person per error |

In every case, the fix is upstream: ensure the right identity strategy is in place and that IDs are linked before the events that need to be connected.

## How to verify identity is correct

Pick any user in PostHog. Click into their person profile. You should see:

1.  **One person, not multiple** – search for their email. If you find two person records, they haven't been merged.
2.  **All expected distinct IDs on the same person** – check the Distinct IDs tab. Both anonymous and identified IDs should be listed.
3.  **Events from all SDKs** – browser events and server events should appear on the same person timeline.
4.  **The `$identify` event** – should appear with both the anonymous and identified distinct IDs, confirming the merge happened.

If you see two separate persons for the same real user, trace backwards: which `identify()` or `alias()` call is missing or happening too late?

**Watch for illegal distinct IDs**

PostHog silently rejects certain distinct IDs during person merges. Blocked values include: `null`, `undefined`, `None`, `0`, `anonymous`, `guest`, `distinct_id`, `id`, `email`, `true`, `false`, `[object Object]`, `NaN`, empty strings, and quoted variants of all of these.

If your application generates IDs that could collide with these values (e.g., the string `"null"` as a user ID), person merges will fail silently. You'll end up with split identities and no error message. Use UUIDs or validate IDs against the blocked list before sending.

## Further reading

-   [Production-ready feature flags](/docs/feature-flags/production-ready.md) – why identity matters for flags and experiments (the pure function model)
-   [Keeping flag evaluations stable](/docs/feature-flags/stable-identity-for-flags.md) – flag-specific mitigations when you can't fully control the identity flow
-   [Identifying users](/docs/product-analytics/identify.md) – PostHog's `identify()` API reference

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better