Skip to content

Persons & identity

A person is the durable profile behind events: one human (as far as Kilden can tell), with mutable traits and one or more distinct ids pointing at them.

Every event names its actor by distinct_id. Two kinds:

  • Anonymous: anon_ + UUID v7, generated by the SDK before you know who the user is.
  • Identified: your own user id, set via identify() or sent server-side.

The format is meaningful: anything that doesn’t look like anon_<uuid7> is treated as an identified user — which is what makes trust levels cheap to compute.

A person can hold many distinct ids (several devices, pre- and post-signup ids), up to a cap of 100.

Traits are the person’s current state — plan, email, company — updated by identify(id, traits), setPersonProperties(), or $set/$set_once properties on any event:

  • $set overwrites.
  • $set_once writes only keys that don’t exist yet.
  • Signed traits (inside a verified identity token) win over unsigned values in the same event.

Trait updates never touch past events; events snapshot their properties forever.

When identify() reveals that two persons are the same human (your user id already existed, and it just got linked from a new anonymous session), they merge:

  • The older person survives; the newer one’s distinct ids re-point to it.
  • On trait conflicts the survivor keeps its values; the merged-away person only fills gaps.
  • Historical events don’t get rewritten (immutability) — queries resolve old person ids to the survivor at read time, so counts stay correct.

Merges are one-directional and conservative: two identified ids are never merged automatically (that pattern is almost always an instrumentation bug), and the web SDK exposes no alias()identify covers the browser case completely.

Analytics queries read events with the person resolved at query time (“person on events” plus a merge-override table), so a merge that happened yesterday retroactively unifies a user’s whole history without any backfill job.