For the complete documentation index, see llms.txt. This page is also available as Markdown.

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_42

anonymous_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.

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

Moment
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_id on logout. Severs the link. Leave it alone.

  • Calling identify(undefined) or identify("") on logout. Ditto.

  • Calling /v1/identify from a pk_* key without Origin header. Same CORS rules as /v1/track. Add the origin to the allowlist or use sk_*.

  • 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

Last updated

Was this helpful?