How OakData knows who's who

How OakData links anonymous activity to identified users, how profiles are matched and merged across devices, and how traits and groups are resolved over time.

OakData follows a visitor anonymously from their very first pageview. When they sign in, that earlier activity is stitched to the identified profile - one continuous history, including everything they did before they had an account.

First visitidentify()Signed inanonymous_iddistinct_id = user id
One visitor, one timeline — pre-sign-in activity stitches onto the identified profile.

The two ids

  • anonymous_id- a random id generated on first visit and stored in the browser. Follows the visitor while they're unknown.
  • distinct_id - the visitor's canonical id. Before identify it equals the anonymous id; after, it becomes the user id you supplied.

identify: anonymous → known

Call identify once you know who the visitor is - right after sign-in or sign-up. It emits an $identify event carrying both the new user id and the previous anonymous id, which is how the server merges the two histories.

identify
ts
oak.identify(user.id, { email: user.email, name: user.name })

From then on oak.getDistinctId() returns the user id, and new events are attributed to the identified person.

Traits

Traits are durable attributes of a person - email, plan, role. Set them via identify's second argument, or directly with set and setOnce. The server replays $identify, $set, and $set_once events chronologically, so a profile always reflects the latest values (setOnce never overwrites an existing key).

traits
ts
oak.set({ plan: 'enterprise' })          // overwrites
oak.setOnce({ signup_source: 'docs' })   // only if not already set

Groups

A group ties a person to an account, company, or workspace - an entity many users share. Group membership and traits resolve the same way person traits do, and surface on the visitor profile.

group
ts
oak.group('company', 'acme-co', { name: 'Acme', plan: 'enterprise' })

Profile matching & merging

Beyond identify, OakData continuously looks for profiles that belong to the same person and surfaces them as related people on the visitor profile. Each candidate is labeled with whyit's related, and only one kind of evidence merges automatically:

  • Same login - merged automatically. Two profiles that ever signed in with the same user id are the same person by definition. Their activity is folded together and the people list shows them as one row, no action needed.
  • Device match - suggested, never automatic. The GPU and CPU fingerprint hashes identify a hardware model, not a specific unit - two factory-identical laptops share them. OakData scores each candidate against cross-browser-stable signals (screen geometry, language, timezone, system appearance and motion preferences, storage quota, peripheral counts) and shows the result as a confidence level with a signal-by-signal comparison. You decide.
  • Same network - context only. Profiles seen on any of the same IPs (a household, office, or VPN) are listed for context but carry no identity weight on their own.

Confirming a match works like a git merge: the secondary profile folds into a master - the identified profile if there is one, else the one with the most events, else the oldest. Stats aggregate across the pair, the people list collapses to the master row (members stay visible under it, tagged Merged), and the underlying events are never rewritten - every decision is reversible from the same dialog.

Why device matches stay manual

Two family members with identical phones on the same Wi-Fi agree on nearly every hardware signal. Person-level signals (dark mode, reduced motion, storage quota) help split them, but only a shared login is proof - so everything short of it stays a suggestion.

alias: merging ids you control

aliaslinks the current distinct id to another id you own. Use it when you mint an id on your backend and need to tie it to the browser's anonymous id - cases identifyalone doesn't cover.

reset: sign-out

On sign-out, call oak.reset(). It clears stored ids, traits, super properties, and groups, then issues a fresh anonymous id - so the next person on a shared device starts clean.

Always reset on logout

Skip reset()and a second user on the same browser inherits the first user's identity, corrupting both profiles. Wire it into your sign-out handler.

Looking someone up

The visitor endpoint (and the get_visitor MCP tool) accept either the distinct id or the anonymous id, and return the resolved profile: user id, traits, groups, attribution, identity timeline, and recent sessions.

How a person is named and pictured

Identified people show their real name and email. An anonymous human gets a stable, friendly alias (like Amber Panda) and an avatar seeded from their distinct id, so you can recognise them across visits without knowing who they are. A bot keeps its real crawler name (like Googlebot) and a robot glyph - never a disguised human face.