User Identity
The Grounded Intelligence SDK manages user identity through anonymous and identified users, with automatic session migration.
Note: The package is installed as
lumina-sdk, but we refer to it as the Grounded Intelligence SDK.
Automatic Anonymous Tracking
When the SDK is initialized, it automatically:
- Checks localStorage for
lumina_id - Checks cookie for
lumina_id - Generates new anonymous ID if not found:
anon_<uuid> - Persists to localStorage and cookie (1 year TTL)
No action needed — tracking starts immediately with an anonymous ID.
// After initialization
Lumina.init({ /* ... */ })
console.log(Lumina.getDistinctId()) // "anon_abc123..."
console.log(Lumina.isAnonymous()) // true
// Start capturing events immediately
const session = await Lumina.session.start()
// User is tracked as anon_abc123...Identifying Users
When a user logs in or signs up, call identify() to merge their anonymous session into a known identity:
Basic Identification
await Lumina.identify('user_12345', {
email: 'jane@example.com',
name: 'Jane Doe',
plan: 'pro'
})
console.log(Lumina.getDistinctId()) // "user_12345"
console.log(Lumina.isAnonymous()) // falseWith Rich Properties
await Lumina.identify('user_12345', {
// Contact info
email: 'jane@example.com',
name: 'Jane Doe',
// Subscription
plan: 'pro',
mrr: 99,
// Metadata
signed_up_at: '2025-01-15T10:30:00Z',
company: 'Acme Corp',
role: 'Product Manager',
// Segmentation
cohort: 'q1_2025',
experiments: ['new_ui', 'ai_suggestions'],
// Location
timezone: 'America/Los_Angeles',
locale: 'en-US'
})What Happens on Identify
distinctIdchanges fromanon_xyztouser_12345- Identify event sent to server
- Server creates
UserAliasto link anonymous → identified - Past events migrated — All anonymous events now queryable under
user_12345 - Future events tagged with new
distinctId - Persistence updated in cookie (primary) and localStorage (backup)
- Session cleared if
opts.newSessionistrueor user changes
// Step 1: User arrives (anonymous)
Lumina.init({ /* ... */ })
console.log(Lumina.getDistinctId()) // "anon_xyz789"
console.log(Lumina.isAnonymous()) // true
// Capture some anonymous events
const session1 = await Lumina.session.start()
// ... user interacts anonymously
// Step 2: User signs up
await Lumina.identify('user_12345', {
email: 'jane@example.com',
name: 'Jane Doe',
plan: 'free'
})
console.log(Lumina.getDistinctId()) // "user_12345"
console.log(Lumina.isAnonymous()) // false
// All past anonymous events now linked to user_12345
// Step 3: User upgrades plan (update properties)
await Lumina.identify('user_12345', {
plan: 'pro', // Updates property
upgraded_at: new Date().toISOString()
})
// Step 4: User logs out
Lumina.reset()
console.log(Lumina.getDistinctId()) // "anon_abc123" (new anonymous ID)
console.log(Lumina.isAnonymous()) // trueUpdating User Properties
Call identify() again with the same user ID to update properties:
// Initial identification
await Lumina.identify('user_12345', {
email: 'jane@example.com',
plan: 'free'
})
// Later: User upgrades
await Lumina.identify('user_12345', {
plan: 'pro',
upgraded_at: '2025-02-01T14:20:00Z'
})
// Later: User changes email
await Lumina.identify('user_12345', {
email: 'jane.doe@newcompany.com',
company: 'New Company Inc'
})Note: Properties are merged, not replaced. To clear a property, set it to null or undefined.
Resetting Identity
Call reset() when a user logs out to create a new anonymous identity:
// User logs out
Lumina.reset()What Happens on Reset
- New anonymous ID generated:
anon_<new_uuid> - Previous identity cleared from cookie and localStorage
- Session trace ID cleared from
sessionStorage - Future events use new ID
Example Logout Flow
async function handleLogout() {
// Clear application session
await clearUserSession()
// Reset Lumina identity
Lumina.reset()
console.log('Logged out')
console.log('New distinct ID:', Lumina.getDistinctId()) // "anon_new123"
console.log('Is anonymous:', Lumina.isAnonymous()) // true
// Redirect to login
router.push('/login')
}API Reference
await Lumina.identify(userId: string, properties?: Record<string, any>, opts?: { newSession?: boolean })
Identify the current user. Switches from anonymous to known identity.
Parameters:
userId: Unique user identifier (e.g., database ID, email)properties: Optional user properties (name, email, etc.)opts.newSession: Iftrue, clears current session and starts fresh
Example:
// After user login
await Lumina.identify('user_123', {
email: 'user@example.com',
name: 'John Doe',
})
// With new session option
await Lumina.identify('user_123', {
email: 'user@example.com',
}, { newSession: true })Behavior:
- Persists
userIdto cookie and localStorage - Updates
distinctIdon all subsequent events - If user changes, clears session by default
- If
opts.newSessionistrue, clears current session trace ID
Lumina.reset()
Reset user identity (e.g., on logout). Generates new anonymous ID.
Returns: void
Example:
Lumina.reset()
console.log(Lumina.isAnonymous()) // true
console.log(Lumina.getDistinctId()) // "anon_abc123" (new)Effects:
- Generates new anonymous ID
- Clears old ID from cookie and localStorage
- Clears session trace ID from
sessionStorage - Future events use new anonymous ID
Lumina.getDistinctId()
Get current distinct ID (user ID or anonymous ID).
Returns: string
Example:
const id = Lumina.getDistinctId()
// "user_12345" (identified) or "anon_abc123" (anonymous)Lumina.isAnonymous()
Check if current user is anonymous.
Returns: boolean
Example:
if (Lumina.isAnonymous()) {
// Show login prompt or guest checkout
console.log('User is browsing anonymously')
} else {
// Show personalized content
console.log('User is logged in as:', Lumina.getDistinctId())
}Best Practices
1. Identify Early
Call identify() as soon as the user logs in or signs up:
// ✅ Good: Identify immediately after login
async function handleLogin(userId: string, userInfo: any) {
// Authenticate user
await authenticateUser(userId)
// Identify in Lumina
await Lumina.identify(userId, {
email: userInfo.email,
name: userInfo.name,
plan: userInfo.plan
})
// Redirect to dashboard
router.push('/dashboard')
}
// ❌ Bad: Never identifying users
// Anonymous tracking is okay, but identified users provide richer analytics2. Include Useful Properties
Add properties that help with segmentation and analysis:
// ✅ Good: Rich properties for segmentation
await Lumina.identify('user_12345', {
email: 'jane@example.com',
name: 'Jane Doe',
plan: 'pro',
company: 'Acme Corp',
role: 'Product Manager',
industry: 'SaaS',
company_size: '50-200',
signed_up_at: '2025-01-15T10:30:00Z',
experiments: ['new_ui', 'ai_suggestions']
})
// ❌ Bad: Minimal properties
await Lumina.identify('user_12345', {
email: 'jane@example.com'
})
// Missed opportunity for segmentation!3. Protect PII
Be mindful of personally identifiable information:
// ✅ Good: Use references instead of raw PII
await Lumina.identify('user_12345', {
email_hash: hashEmail(user.email), // Hash sensitive data
name: user.name, // OK if needed
plan: user.plan, // Not PII
account_id: user.accountId // Reference
})
// ⚠️ Caution: Storing PII
await Lumina.identify('user_12345', {
email: 'jane@example.com', // Consider if necessary
phone: '+1-555-123-4567', // Sensitive
ssn: '123-45-6789' // Never do this!
})4. Reset on Logout
Always reset identity when user logs out:
// ✅ Good: Reset on logout
function handleLogout() {
clearSession()
Lumina.reset() // Generate new anonymous ID
router.push('/login')
}
// ❌ Bad: Never resetting
// Old user identity persists for next visitor!5. Check Identity State
Use isAnonymous() to conditionally show features:
// ✅ Good: Show appropriate UI based on identity
function ChatHeader() {
const isAnon = Lumina.isAnonymous()
return (
<div>
<h1>Chat</h1>
{isAnon ? (
<button onClick={showLoginPrompt}>
Sign in to save history
</button>
) : (
<span>
Logged in as {Lumina.getDistinctId()}
</span>
)}
</div>
)
}Common Patterns
Login Flow
async function handleLogin(credentials: { email: string; password: string }) {
try {
// Authenticate user
const user = await authenticateUser(credentials)
// Identify in Lumina
await Lumina.identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan,
logged_in_at: new Date().toISOString()
})
console.log('User identified:', Lumina.getDistinctId())
// Redirect
router.push('/dashboard')
} catch (error) {
console.error('Login failed:', error)
}
}Signup Flow
async function handleSignup(data: { email: string; name: string; password: string }) {
try {
// Create user account
const user = await createUser(data)
// Identify immediately
await Lumina.identify(user.id, {
email: user.email,
name: user.name,
plan: 'free',
signed_up_at: new Date().toISOString(),
source: 'organic',
cohort: 'q1_2025'
})
console.log('User signed up and identified:', Lumina.getDistinctId())
// Start onboarding
router.push('/onboarding')
} catch (error) {
console.error('Signup failed:', error)
}
}Logout Flow
async function handleLogout() {
try {
// Clear application session
await clearUserSession()
// Reset Lumina identity
Lumina.reset()
console.log('User logged out')
console.log('New anonymous ID:', Lumina.getDistinctId())
// Redirect to login
router.push('/login')
} catch (error) {
console.error('Logout failed:', error)
}
}Check Auth on Mount
import { useEffect, useState } from 'react'
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false)
useEffect(() => {
// Check if user is authenticated
const checkAuth = async () => {
const user = await getCurrentUser()
if (user) {
// User is logged in, identify them
await Lumina.identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan
})
setIsAuthenticated(true)
} else {
// User is not logged in, ensure anonymous
if (!Lumina.isAnonymous()) {
Lumina.reset()
}
setIsAuthenticated(false)
}
}
checkAuth()
}, [])
return <div>{/* App content */}</div>
}Troubleshooting
Identity Not Persisting
Problem: Identity resets on page reload
Solution: Ensure cookies and localStorage are enabled:
// Check if storage is available
if (typeof window !== 'undefined' && window.localStorage) {
console.log('localStorage available')
} else {
console.warn('localStorage not available - identity may not persist')
}Multiple Identities
Problem: Same user tracked under multiple IDs
Solution: Always call identify() with the same user ID:
// ✅ Good: Consistent user ID
await Lumina.identify('user_12345', { /* ... */ })
// ❌ Bad: Different IDs for same user
await Lumina.identify('user_12345', { /* ... */ })
await Lumina.identify('bob@example.com', { /* ... */ }) // Creates duplicate!Old Identity After Logout
Problem: Previous user's identity persists
Solution: Always call reset() on logout:
// ✅ Good: Reset on logout
Lumina.reset()
// ❌ Bad: Forgot to reset
// Next user sees previous user's data!Next Steps
- Capturing Anchors — Learn to capture events
- PII Masking — Protect sensitive user data
- Initialization — Configure the SDK