Identity

Two fields, nothing inferred, and why a unique is only ever counted inside one source.

Identity here is two fields and no inference. Every number that counts people rather than events rests on them.

FieldComes fromWhat it is
distinct_idThe client, on every eventA column. Anonymous and scoped to one source: a visitor id in a browser, an install id on a machine, whatever your server passes per call.
user.idOnly ever the string you passed to identify()An attribute. Your id and your meaning. It is never invented, derived, looked up, or filled in from anything else.

A unique is counted inside one source

One definition, everywhere: count(distinct coalesce(user.id, distinct_id)), scoped to a single source. An identified client folds into its user; an anonymous one stands on its own.

Never add uniques across sources. The same human on your website and in your app is two uniques, and that is the correct answer rather than a bug. The two ids were generated in different places and have never met, so a figure totalled across sources counts that person twice. Nothing on screen will look wrong. Read each source on its own.

Sources are never linked

Two sources in one project are two separate anonymous id spaces reported next to each other. A project is a namespace for events, sources and boards; it is not an identity namespace. There is no inference, no probabilistic matching, no IP or fingerprint heuristic, and no merging, ever.

If you want one person joined across two sources, call identify() with the same id on both. That is the only link there is, and it is your data and your decision rather than something reconstructed from behaviour. It applies to the events that carry the id: nothing is merged retroactively.

The id names an installation

A desktop client keeps distinct_id in per-user local application data, not the roaming profile, because it names one installation rather than one person. Carrying it to a second machine would report two installs as one, which is what identify() is for and what an anonymous id must not do quietly.

Late events count when they happened

time is stamped by the client at the moment the thing happened, and it is authoritative. A laptop that was offline until Monday still reports Friday’s launch as Friday. Every series and every bucket is built on that, never on when the event arrived.