SDK & frameworks
Identifying sessions
Tag sessions with safe user, account, or cohort properties so friction data is segmentable.
Tag sessions with safe user, account, or cohort properties so friction data is segmentable. Once you're calling identify(), you can ask whether checkout friction is worse for Grow users than Scale users, whether a redesign cohort is hitting the same issues as the control group, or whether enterprise accounts behave differently during onboarding.
Without it, you're looking at aggregate confusion scores with no way to slice them.
identify()
Call it after login, when you have stable properties:
import { identify } from 'flusterduck'
identify({
user_id: 'usr_8f3a2c91',
plan: 'scale',
org_id: 'org_4b2e9f1c',
account_age_days: '42',
})identify() accepts one object. Values can be strings, numbers, or booleans; each is coerced to a string and truncated to 256 characters. Keys are capped at 64 characters, and only the first 20 keys in a single call are kept. Keep IDs opaque. A database primary key or UUID is fine. An email address, display name, or phone number is not.
Two filters run in the browser before any of it ships. A trait is dropped, silently, if its key reads as PII (anything containing email, phone, name, address, ssn, dob, card, password, token, auth, or similar segments) or its value looks like contact information (an @, or a long digit run). If a trait you passed isn't showing up, check the key name first: identify({ email: 'wrong' }) doesn't error, it just never leaves the browser.
The string shorthand
When all you want is to tag the session with your own user reference, pass a single string:
identify('usr_8f3a2c91')This sets the session's user_ref and merges with any properties set earlier, it never clobbers them. The ref must be opaque: values that look like contact information (anything containing an @, or a long digit run like a phone or card number) are refused in the browser and never sent.
The user_ref is what powers the resolution loop: when Flusterduck verifies that a fix worked after a deploy, the issue.verified webhook delivers the refs of exactly the users who hit that issue, so your own systems can tell them it's fixed. See Resolution loop. Setting the ref through the object form of identify() instead of the string shorthand works too: use the key user_ref (user and uid are also recognized).
What properties to pass
Pass things you'd want to filter by when investigating a friction pattern:
identify({
user_id: 'usr_8f3a2c91',
plan: 'scale',
billing: 'annual',
org_size: '11-50',
role: 'admin',
account_age_days: '42',
experiment_variant: 'checkout-v2',
feature_flag_new_nav: 'true',
})Never pass: email, name, username, IP address, display handle, or any field that links directly to a real person.
When to call it
Call identify() as early as possible after login and after the SDK has initialized. Properties apply to the current session metadata used for future flushes.
In practice: put it in a component or hook that runs immediately after your auth state resolves.
React
import { useEffect } from 'react'
import { identify } from 'flusterduck'
export function IdentityBridge({ userId, plan, orgId }: {
userId?: string
plan?: string
orgId?: string
}) {
useEffect(() => {
if (!userId) return
identify({
user_id: userId,
plan: plan ?? 'trial',
org_id: orgId ?? '',
})
}, [userId, plan, orgId])
return null
}Render this inside your auth provider where userId is reliably available.
Next.js
'use client'
import { useEffect } from 'react'
import { identify } from 'flusterduck'
export function IdentifyUser({ userId, plan }: { userId?: string; plan?: string }) {
useEffect(() => {
if (!userId) return
identify({ user_id: userId, plan: plan ?? 'trial' })
}, [userId, plan])
return null
}Vue
<script setup lang="ts">
import { watch } from 'vue'
import { identify } from 'flusterduck'
const props = defineProps<{ userId?: string; plan?: string }>()
watch(
() => props.userId,
(userId) => {
if (!userId) return
identify({ user_id: userId, plan: props.plan ?? 'trial' })
},
{ immediate: true },
)
</script>The segment config option
For properties that apply to every session regardless of who's logged in, use segment in your SDK config instead of identify():
init({
key: process.env.NEXT_PUBLIC_FLUSTERDUCK_KEY!,
segment: {
app_version: process.env.NEXT_PUBLIC_APP_VERSION ?? 'unknown',
region: 'us-east-1',
experiment_group: 'pricing-v2-b',
},
})segment properties are attached starting at SDK init. identify() properties are attached after the call, and the object form replaces the whole trait set each time, including whatever segment set at init. Call identify() with an object and your segment config is gone from that point on, not just the keys that collided.
Use segment for: deploy tags, region, A/B test cohorts, feature flag variants at the session level.
Use identify() for: plan tier, organization, user role, account age, anything tied to a specific authenticated user. If you want both to survive, fold your segment values into the object you pass to identify().
Using segments in investigations
With properties in place, you can filter friction data by segment values. Via MCP:
Are rage clicks on the checkout button worse for Grow plan users than Scale?
Which sessions hitting form abandonment on onboarding are enterprise accounts?
Compare confusion scores between experiment_group: pricing-v2-a and pricing-v2-b.
Show me sessions where role: admin hit the dead click on the settings page.Multiple identify() calls
Only the string shorthand merges. identify('usr_8f3a2c91') preserves whatever traits are already set and only touches user_ref.
The object form does not merge: each call replaces the entire trait set, including anything set by an earlier identify() call or by segment in your config. Calling it twice with different objects loses the first call's traits, not just the keys that didn't repeat.
// Wrong: the second call wipes out user_id and plan
identify({ user_id: 'usr_8f3a2c91', plan: 'scale' })
identify({ org_id: 'org_4b2e9f1c', org_size: '11-50' })
// session ends up with only org_id and org_size
// Right: pass everything you want kept, every time
let traits = { user_id: 'usr_8f3a2c91', plan: 'scale' }
identify(traits)
traits = { ...traits, org_id: 'org_4b2e9f1c', org_size: '11-50' }
identify(traits)Keep your own copy of the traits object in application state (a ref, a store, whatever you already use) and call identify() with the full set each time you load new data, not just the fields that changed.