Identity & sessions
The SDK needs an identity to key cadence records against — “once per user”, “quiet for 30 days after they answer”. Most visitors are anonymous, so it maintains one for them, and hands the records over when they log in.
Anonymous ids
On first visit the SDK mints a random anonymous id and stores it in localStorage under intunix:anon. It is used whenever no user has been identified:
const uid = user?.id ?? anonId()It is a random opaque string. It contains nothing about the person, is not shared across devices or browsers, and is never sent anywhere except on the responses and events that visitor produces.
identify()
Call it when you know who the user is — at login, and on every app boot where the session is already authenticated.
client.identify('user_42', { plan: 'pro', role: 'admin', signupYear: 2024 })The traits become available to trait targeting. They accept strings, numbers, booleans, and null.
They are not persisted across page loads. If you only call identify() inside your login handler, then after any reload the user is anonymous again and every trait-based targeting rule stops matching. Call it wherever your app establishes who the user is.
What it does to existing records
This is the important part. identify() does not merely start writing under a new key — it moves everything recorded under the anonymous id onto the real one.
// Anonymous visitor dismisses the NPS form. Recorded under intunix:anon.
// They log in:
client.identify('user_42')
// The dismissal now belongs to user_42. The anonymous key is empty again.So the anonymous id only ever holds activity since the last identify(). A form someone dismissed while logged out still counts against them after they log in, which is what you want — otherwise logging in would be a way to be re-asked immediately.
reset()
Call it on logout. It does four things:
- Forgets the identified user and their traits.
- Rotates the anonymous id to a fresh one.
- Ends the current session.
- Clears sticky context.
A common misconception. identify() already handles that, because it drains the anonymous key on every login — a returning user's caps live under their own id, not a shared anonymous one. If you call identify() on every login, gating stays correct even if you never call reset().
So what does skipping it cost?
It matters on a shared browser, where a second person uses the same device:
| Without reset() | Consequence |
|---|---|
| Session is not ended | The next person lands inside the previous session (30-minute TTL), so every_session forms will not re-fire for them. |
| Sticky context survives | Stored in sessionStorage, so the previous user's context attaches to the next person's responses until the tab is closed. |
| Anonymous id is not rotated | Both people's logged-out responses carry the same anonymous_id, so reporting counts two humans as one respondent. |
The last one is the reason to bother. The first two self-heal when the session expires or the tab closes. A response, though, is written once and keeps whatever ids it had at submit time — permanently.
reset() matters more if your logout is a client-side transition with no page reload. The identified user is held in memory, so without it the SDK keeps attributing responses to the previous user until the next identify() lands.
What reset() deliberately keeps
The form catalog, and the per-device firstSeenAt and sessionCount that eligibility.minSessions and minDaysSinceFirstSeen gate on. Those describe the device, not the account. Clearing them would let anyone reset themselves to a brand-new user by logging out.
Sessions
A session lasts 30 minutes of inactivity. Tracked events and navigation renew it; a longer gap starts a new session and increments the session count.
A new session triggers three things:
- The form catalog is re-fetched (once, for the registered form set).
- Sticky context is cleared.
every_sessioncadences re-arm.
What is stored on the device
| Key | Storage | Contents |
|---|---|---|
intunix:anon | localStorage | Anonymous id. |
intunix:session | localStorage | Current session id and its 30-minute expiry. |
intunix:profile | localStorage | First-seen timestamp, session count, per-form outcomes. |
intunix:shown | localStorage | Per-form show counts and timestamps — the cadence ledger. |
intunix:experiences:v2 | localStorage | Cached form catalog, stamped with session and registered set. |
intunix:context | sessionStorage | Sticky context bag. |
No cookies are set, and nothing is written to a third-party domain. If storage is unavailable the SDK falls back to memory — forms still show, but cadence resets on every page load.
Putting it together
// On login — and on every boot where the user is already authenticated.
function onAuthenticated(user) {
client.identify(user.id, {
plan: user.plan,
role: user.role,
})
}
// On logout.
function onLogout() {
client.reset()
}