Link anonymous → known users
Stitching pre-login activity to the post-login user, including multi-device.
A visitor lands from a Facebook ad on Tuesday. Browses for a week. Signs up on the following Monday. You want to credit that signup to the original Facebook click — and to know all the things they did before signing in.
This is the canonical "anonymous-to-known" stitch.
What's happening under the hood
Day 1 14:00 $pageview anon=u_abc user=NULL source=facebook/paid
Day 1 14:02 clicked_pricing anon=u_abc user=NULL source=facebook/paid
Day 5 09:30 $pageview anon=u_abc user=NULL source=direct/direct
Day 7 11:14 $identify anon=u_abc user=user_42 ← here
Day 7 11:14 signup anon=u_abc user=user_42
Day 9 16:22 feature_used anon=u_abc user=user_42anonymous_id is the same throughout. user_id only appears from the $identify onward. Historical events for u_abc aren't rewritten — they remain user_id = NULL. Stitching is a query-time join.
Step 1 — call /v1/identify on signup or login
The single most important moment.
From the browser
import { identify } from "@millimetric/track";
// after your auth flow resolves
identify(user.id, { email: user.email, plan: user.plan });The browser SDK emits a $identify event and tags every subsequent track() with user_id.
From the server (recommended for signups)
When the user is created in your DB, also POST identify:
(See Server-side events for how to thread anonymous_id.)
Step 2 — use the same anonymous_id post-login
After identify, the browser SDK keeps the same anonymous_id. Don't generate a new one — that breaks the link.
For server-rendered apps, persist the id in a first-party cookie (see server-side recipe) so it survives logout/login on the same device.
Step 3 — stitch in queries
Three increasingly thorough patterns.
3a. First-touch source per user
For "where did user_42 come from?" — find the earliest event for any anonymous_id they've ever been associated with.
This works for multi-device users too — every device's anonymous_id is in user_anons because they all eventually called identify.
3b. Pre-login behaviour for one user
What was user_42 doing before they signed up?
3c. Time-to-conversion per user
Multi-device
Every device the user signs in on emits its own $identify, linking that device's anonymous_id to user_id. The "anonymous_id ↔ user_id" relation becomes many-to-one over time, which is exactly what you want.
When NOT to call identify
Anonymous browsing
no
User signs up (creates account)
yes
User logs in (returning)
yes — confirms link on this device
User logs out
no — keep tracking events anonymously, but don't reset anonymous_id
User switches account
yes — call identify with the new user_id
Calling identify on every page is fine. It's idempotent at the data level — you'll just have more $identify events.
Common pitfalls
Generating a new
anonymous_idon logout. Severs the link. Leave it alone.Calling
identify(undefined)oridentify("")on logout. Ditto.Calling
/v1/identifyfrom apk_*key withoutOriginheader. Same CORS rules as/v1/track. Add the origin to the allowlist or usesk_*.Expecting historical events to be rewritten. They aren't. The stitch is always a query-time join. That's fine — ClickHouse joins on
(project_id, anonymous_id)are very fast.
See also
Identities — concepts.
POST /v1/identify — the endpoint.
Sessions — entry-source per visit.
GDPR right-to-be-forgotten — what
forgetdoes to anonymous events.
Last updated
Was this helpful?