Skip to content
fullstory-anonymize-users logo

Fullstory Anonymize Users API

fullstory-anonymize-users

Comprehensive guide for implementing Fullstory's User Anonymization API (setIdentity with anonymous:true) for web applications. Teaches proper logout handling, session management, privacy compliance, and user switching scenarios. Includes detailed good/bad examples for logout flows, multi-user ap...

SKILL.md

Full skill instructions

Fullstory Anonymize Users API

Overview

Fullstory's Anonymize Users API allows developers to release the identity of the current user and create a new anonymous session. This is essential for:

  • User Logout: Properly ending an identified session when a user logs out
  • Account Switching: Allowing users to switch between accounts cleanly
  • Privacy Compliance: Implementing "forget me" or privacy-conscious features
  • Shared Devices: Ensuring one user's session doesn't bleed into another's

When you call FS('setIdentity', { anonymous: true }), the current session ends and a fresh anonymous session begins. The previously identified user remains in Fullstory's records, but subsequent activity is no longer linked to them.

Core Concepts

What Happens When You Anonymize

  1. Current session is closed and marked as belonging to the identified user
  2. A new fs_uid cookie is generated - breaking the link to all previous sessions
  3. New anonymous session begins with a new session ID and new cookie
  4. Previous user data is preserved - anonymizing doesn't delete history
  5. Subsequent activity is anonymous until a new setIdentity call

Cookie Behavior: Normally, the fs_uid first-party cookie (1-year expiry) links all sessions from the same browser together. When anonymize is called, Fullstory generates a new fs_uid cookie, effectively creating a "new device" from Fullstory's perspective. Any future setIdentity calls will only merge sessions from the new cookie, not the old one.

Reference: Why Fullstory uses First-Party Cookies

Session Lifecycle

┌─────────────┐  Login   ┌─────────────┐  Logout  ┌─────────────┐
│  Anonymous  │ ───────► │ Identified  │ ───────► │ New Anon    │
│  Session A  │          │  Session B  │          │  Session C  │
└─────────────┘          └─────────────┘          └─────────────┘
                              │
              setIdentity     │    setIdentity
              (uid: 'xxx')    │    (anonymous: true)

When to Anonymize

ScenarioShould Anonymize?Reason
User logs out✅ YesPrevents session attribution to wrong user
User switches accounts✅ YesClean slate before new identification
User requests data deletion❓ ConsiderPart of broader privacy implementation
User clears browser data❌ NoFullstory handles this automatically
Page navigation❌ NoIdentity persists across pages
Session timeout❓ DependsBased on your security requirements

API Reference

Basic Syntax

FS('setIdentity', {anonymous: true})

Parameters

ParameterTypeRequiredDescription
anonymousbooleanYesMust be true to anonymize the user

Rate Limits

  • Sustained: 30 calls per page per minute
  • Burst: 10 calls per second

Async Version

await FS('setIdentityAsync', {anonymous: true})

✅ GOOD IMPLEMENTATION EXAMPLES

Example 1: Basic Logout Handler

// GOOD: Proper logout with Fullstory anonymization
async function handleLogout() {
  try {
    // 1. Call your backend logout endpoint
    await fetch('/​api/​auth/​logout', {method: 'POST'})

    // 2. Clear local authentication state
    clearAuthTokens()
    clearUserState()

    // 3. Anonymize in Fullstory BEFORE redirecting
    FS('setIdentity', {anonymous: true})

    // 4. Redirect to login page
    window.location.href = '/​login'
  } catch (error) {
    console.error('Logout failed:', error)
    // Still anonymize even if backend fails
    FS('setIdentity', {anonymous: true})
    window.location.href = '/​login'
  }
}

Why this is good:

  • ✅ Anonymizes before redirect
  • ✅ Handles errors gracefully
  • ✅ Clears local state before anonymizing
  • ✅ Ensures next user won't be associated with previous user

Example 2: React Logout with Auth Context

// GOOD: React hook pattern for logout with Fullstory
import {useCallback} from 'react'
import {useAuth} from './​auth-context'
import {useNavigate} from 'react-router-dom'

function useLogout() {
  const {clearAuth} = useAuth()
  const navigate = useNavigate()

  const logout = useCallback(async () => {
    // Track the logout action before anonymizing
    FS('trackEvent', {
      name: 'User Logged Out',
      properties: {
        logoutMethod: 'manual',
        sessionDuration: getSessionDuration(),
      },
    })

    // Clear application auth state
    await clearAuth()

    // Anonymize the Fullstory session
    FS('setIdentity', {anonymous: true})

    // Navigate to home/​login
    navigate('/​login')
  }, [clearAuth, navigate])

  return logout
}

// Usage
function LogoutButton() {
  const logout = useLogout()
  return <button onClick={logout}>Sign Out</​button>
}

Why this is good:

  • ✅ Tracks logout event before anonymizing (preserves attribution)
  • ✅ Integrated with React ecosystem
  • ✅ Reusable across components
  • ✅ Clean navigation after logout

Example 3: Account Switching

// GOOD: Clean account switching with proper session boundaries
async function switchAccount(newAccountId) {
  const newUser = await fetchAccountDetails(newAccountId)

  // Track the switch event under current user
  FS('trackEvent', {
    name: 'Account Switch Initiated',
    properties: {
      targetAccountId: newAccountId,
    },
  })

  // Step 1: Anonymize current session
  FS('setIdentity', {anonymous: true})

  // Step 2: Set up new account context
  await setupAccountContext(newUser)

  // Step 3: Identify as new user
  FS('setIdentity', {
    uid: newUser.id,
    properties: {
      displayName: newUser.name,
      email: newUser.email,
      accountType: newUser.type,
    },
  })

  // Refresh UI
  window.location.reload()
}

Why this is good:

  • ✅ Tracks event before identity change
  • ✅ Cleanly separates sessions between accounts
  • ✅ No data contamination between accounts
  • ✅ New user gets fresh identification

Example 4: Session Timeout Handler

// GOOD: Handling session timeout with Fullstory
class SessionManager {
  constructor() {
    this.timeoutDuration = 30 * 60 * 1000 // 30 minutes
    this.timeoutId = null
    this.lastActivity = Date.now()
  }

  startTimeout() {
    this.resetTimeout()
    document.addEventListener('click', () => this.resetTimeout())
    document.addEventListener('keypress', () => this.resetTimeout())
  }

  resetTimeout() {
    this.lastActivity = Date.now()
    if (this.timeoutId) clearTimeout(this.timeoutId)

    this.timeoutId = setTimeout(() => {
      this.handleSessionTimeout()
    }, this.timeoutDuration)
  }

  async handleSessionTimeout() {
    // Track timeout event while still identified
    FS('trackEvent', {
      name: 'Session Timeout',
      properties: {
        inactivityDuration: Date.now() - this.lastActivity,
        lastPage: window.location.pathname,
      },
    })

    // Anonymize the session
    FS('setIdentity', {anonymous: true})

    // Clear auth and redirect
    clearAuthState()
    showTimeoutModal()
  }
}

Why this is good:

  • ✅ Tracks timeout before anonymizing
  • ✅ Captures useful debugging info
  • ✅ Clean session boundary on timeout
  • ✅ User feedback via modal

Example 5: Privacy-Conscious Implementation

// GOOD: "Incognito mode" toggle for privacy-conscious users
class PrivacyManager {
  constructor() {
    this.isIncognitoMode = false
    this.originalUserId = null
  }

  async enableIncognitoMode(currentUserId) {
    // Store original user ID for potential re-identification
    this.originalUserId = currentUserId
    this.isIncognitoMode = true

    // Track before anonymizing
    FS('trackEvent', {
      name: 'Incognito Mode Enabled',
      properties: {},
    })

    // Anonymize - activity won't be linked to user
    FS('setIdentity', {anonymous: true})

    // Update UI
    showIncognitoIndicator()
  }

  async disableIncognitoMode() {
    if (!this.originalUserId) return

    this.isIncognitoMode = false

    // Re-identify user
    const user = await getCurrentUser()
    FS('setIdentity', {
      uid: user.id,
      properties: {
        displayName: user.name,
        email: user.email,
      },
    })

    // Track re-enablement
    FS('trackEvent', {
      name: 'Incognito Mode Disabled',
      properties: {},
    })

    hideIncognitoIndicator()
    this.originalUserId = null
  }
}

Why this is good:

  • ✅ Gives users control over tracking
  • ✅ Maintains ability to re-identify
  • ✅ Clear user feedback
  • ✅ Events tracked at session boundaries

❌ BAD IMPLEMENTATION EXAMPLES

Example 1: Forgetting to Anonymize on Logout

// BAD: No Fullstory anonymization on logout
function handleLogout() {
  clearAuthTokens()
  clearUserState()
  window.location.href = '/​login'
  // Missing FS('setIdentity', { anonymous: true })!
}

Why this is bad:

  • ❌ Next user's activity may be attributed to previous user
  • ❌ Session continues under wrong identity
  • ❌ Data integrity issues in analytics
  • ❌ Privacy concern if sharing device

CORRECTED VERSION:

// GOOD: Include Fullstory anonymization
function handleLogout() {
  clearAuthTokens()
  clearUserState()

  // Anonymize before redirect
  FS('setIdentity', {anonymous: true})

  window.location.href = '/​login'
}

Example 2: Anonymizing After Redirect

// BAD: Anonymizing after navigation starts
function handleLogout() {
  window.location.href = '/​login'

  // BAD: This may never execute - page is already navigating!
  FS('setIdentity', {anonymous: true})
}

Why this is bad:

  • ❌ Navigation starts before anonymization
  • ❌ FS call may not complete
  • ❌ Session may not properly close

CORRECTED VERSION:

// GOOD: Anonymize BEFORE navigation
async function handleLogout() {
  // Anonymize first
  await FS('setIdentityAsync', {anonymous: true})

  // Then navigate
  window.location.href = '/​login'
}

Example 3: Anonymizing Repeatedly

// BAD: Calling anonymize multiple times
function handleLogout() {
  // Excessive calls
  FS('setIdentity', {anonymous: true})
  FS('setIdentity', {anonymous: true})
  FS('setIdentity', {anonymous: true})

  window.location.href = '/​login'
}

Why this is bad:

  • ❌ Wastes API call quota
  • ❌ Creates unnecessary session splits
  • ❌ May hit rate limits
  • ❌ No benefit from multiple calls

CORRECTED VERSION:

// GOOD: Single anonymization call
function handleLogout() {
  FS('setIdentity', {anonymous: true})
  window.location.href = '/​login'
}

Example 4: Using Wrong Parameter

// BAD: Wrong way to anonymize
FS('setIdentity', {uid: null}) // BAD: uid shouldn't be null
FS('setIdentity', {uid: 'anonymous'}) // BAD: This identifies as user "anonymous"!
FS('setIdentity', {uid: ''}) // BAD: Empty string uid
FS('setIdentity', {}) // BAD: Missing required parameters

Why this is bad:

  • ❌ uid: null may cause errors
  • ❌ uid: 'anonymous' creates an identified user named "anonymous"
  • ❌ Empty string uid is invalid
  • ❌ Empty object doesn't anonymize

CORRECTED VERSION:

// GOOD: Proper anonymization syntax
FS('setIdentity', {anonymous: true})

Example 5: Anonymizing Without Tracking Important Events

// BAD: Missing opportunity to track logout event
function handleLogout() {
  // Just anonymizing without capturing useful data
  FS('setIdentity', {anonymous: true})
  window.location.href = '/​login'
}

Why this is bad:

  • ❌ No record of intentional logout vs session timeout
  • ❌ Can't analyze logout patterns
  • ❌ Loses attribution for the logout event itself

CORRECTED VERSION:

// GOOD: Track event before anonymizing
function handleLogout() {
  // Track while still identified
  FS('trackEvent', {
    name: 'User Logged Out',
    properties: {
      logoutMethod: 'user_initiated',
      pageAtLogout: window.location.pathname,
    },
  })

  // Then anonymize
  FS('setIdentity', {anonymous: true})
  window.location.href = '/​login'
}

Example 6: Anonymizing During Errors Instead of Proper Handling

// BAD: Using anonymization to "hide" errors
function handleError(error) {
  // Don't use anonymization to hide error attribution!
  FS('setIdentity', {anonymous: true})
  console.error(error)
}

Why this is bad:

  • ❌ Loses error attribution to user for debugging
  • ❌ Makes it harder to help affected users
  • ❌ Misuse of anonymization API
  • ❌ Creates confusing session boundaries

CORRECTED VERSION:

// GOOD: Log errors while identified, only anonymize on logout
function handleError(error) {
  // Track the error - attribution helps debugging!
  FS('trackEvent', {
    name: 'Application Error',
    properties: {
      errorMessage: error.message,
      errorCode: error.code,
      page: window.location.pathname,
    },
  })

  // Show error UI without anonymizing
  showErrorMessage(error)
}

COMMON IMPLEMENTATION PATTERNS

Pattern 1: Logout Service

// Centralized logout service
class LogoutService {
  static async logout(options = {}) {
    const {trackEvent = true, redirectUrl = '/​login', reason = 'user_initiated'} = options

    // Track logout if requested
    if (trackEvent) {
      FS('trackEvent', {
        name: 'User Logged Out',
        properties: {
          reason: reason,
          sessionDuration: getSessionDuration(),
        },
      })
    }

    // Backend logout
    try {
      await fetch('/​api/​logout', {method: 'POST'})
    } catch (e) {
      console.warn('Backend logout failed:', e)
    }

    // Clear client state
    clearAuthTokens()
    clearUserState()
    clearLocalStorage()

    // Anonymize Fullstory
    FS('setIdentity', {anonymous: true})

    // Redirect
    if (redirectUrl) {
      window.location.href = redirectUrl
    }
  }
}

// Usage
await LogoutService.logout()
await LogoutService.logout({reason: 'session_timeout', redirectUrl: '/​timeout'})

Pattern 2: Multi-Tenant Application

// For apps with workspace/​tenant switching
class TenantManager {
  async switchTenant(newTenantId) {
    const currentUser = getCurrentUser()

    // Track switch under current context
    FS('trackEvent', {
      name: 'Tenant Switch',
      properties: {
        fromTenant: currentUser.tenantId,
        toTenant: newTenantId,
      },
    })

    // Start fresh session for new tenant context
    FS('setIdentity', {anonymous: true})

    // Update tenant context
    await loadTenantContext(newTenantId)

    // Re-identify with new tenant context
    FS('setIdentity', {
      uid: currentUser.id,
      properties: {
        displayName: currentUser.name,
        email: currentUser.email,
        tenantId: newTenantId,
        tenantName: await getTenantName(newTenantId),
      },
    })
  }
}

Pattern 3: Kiosk/​Shared Device Mode

// For shared devices like kiosks
class KioskMode {
  sessionTimeout = 5 * 60 * 1000 // 5 minutes

  async startSession(user) {
    FS('setIdentity', {
      uid: user.id,
      properties: {
        displayName: user.name,
        deviceMode: 'kiosk',
        locationId: getKioskLocation(),
      },
    })

    // Auto-logout after timeout
    setTimeout(() => this.endSession(), this.sessionTimeout)
  }

  async endSession() {
    FS('trackEvent', {
      name: 'Kiosk Session Ended',
      properties: {
        endReason: 'timeout',
        location: getKioskLocation(),
      },
    })

    FS('setIdentity', {anonymous: true})

    // Reset to welcome screen
    showWelcomeScreen()
  }
}

RELATIONSHIP WITH OTHER FS APIs

Anonymize vs setProperties

// setProperties updates user info without changing identity
FS('setProperties', {
  type: 'user',
  properties: {lastActiveAt: new Date().toISOString()},
})

// anonymous: true ends the session entirely
FS('setIdentity', {anonymous: true}) // New session starts

Anonymize + Re-identify Flow

// Common pattern: switch users
FS('setIdentity', {anonymous: true}) // End session 1

// Some time passes, new user logs in

FS('setIdentity', {
  // Start session 2
  uid: newUser.id,
  properties: {displayName: newUser.name},
})

TROUBLESHOOTING

Session Not Properly Ending

Symptom: New user activity appears under old user

Common Causes:

  1. ❌ Anonymize called after page navigation starts
  2. ❌ Anonymize never called (missing from logout flow)
  3. ❌ Using wrong syntax (uid: null instead of anonymous: true)

Solutions:

  • ✅ Use async version and await completion
  • ✅ Audit all logout paths to ensure anonymization
  • ✅ Use { anonymous: true } syntax

Too Many Session Splits

Symptom: User has many short, fragmented sessions

Common Causes:

  1. ❌ Calling anonymize on every page load
  2. ❌ Calling anonymize on errors
  3. ❌ Multiple anonymize calls in single flow

Solutions:

  • ✅ Only anonymize on actual logout/​switch events
  • ✅ Audit code for unintended anonymize calls
  • ✅ Use single anonymize call per logout flow

LIMITS AND CONSTRAINTS

Call Frequency

  • Sustained: 30 calls per page per minute
  • Burst: 10 calls per second
  • Single call per logout is sufficient

Session Behavior

  • Anonymization creates a new session immediately
  • Previous session data remains intact and attributed
  • Subsequent activity is anonymous until next identification

KEY TAKEAWAYS FOR AGENT

When helping developers implement User Anonymization:

  1. Always emphasize:

    • Anonymize BEFORE navigation/​redirects
    • Use { anonymous: true } syntax exactly
    • Track important events BEFORE anonymizing
    • Single call per logout is sufficient
  2. Common mistakes to watch for:

    • Forgetting to anonymize on logout
    • Anonymizing after redirect starts
    • Using wrong syntax (uid: null, uid: 'anonymous')
    • Over-calling anonymize
    • Missing trackEvent before anonymize
  3. Questions to ask developers:

    • What are all the logout/​signout paths in your app?
    • Do users switch between accounts?
    • Is this a shared device scenario?
    • What events should be tracked before logout?
  4. Integration considerations:

    • Must anonymize in all logout paths
    • Consider session timeout handling
    • Account switching needs anonymize between identifications
    • Order matters: track events → anonymize → redirect

REFERENCE LINKS


This skill document was created to help Agent understand and guide developers in implementing Fullstory's User Anonymization API correctly for web applications.

More Security skills

supabase logo
Security

supabase

Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...

2.7K 308.9K
View

Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.

98 216.2K
View

Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.

462 163.8K
View

A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.

462 127.7K
View

Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.

462 125.1K
View

Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.

222 118.2K
View

Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".

31.9K 116.1K
View
cloudflare logo
Security

cloudflare

Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.

3K 110.7K
View

Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.

253 103K
View
gws-gmail logo
Security

gws-gmail

Send, read, and manage Gmail messages, drafts, labels, and account settings.

31.2K 78.9K
View

Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.

94 72.3K
View
gws-shared logo
Security

gws-shared

Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.

31.2K 63.4K
View

Security AI tools

StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
GeoSpy logo
Security

GeoSpy

GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams

Free
View

Detect AI-generated voices to protect against audio fraud.

Paid
View

Ensure Your Content's Originality with AI Plagiarism Checker

Freemium
View
Img Upscaler logo
Security

Img Upscaler

Upscale images by 400% without quality loss

Freemium
View
AICheatCheck logo
Security

AICheatCheck

Accurately Detect AI-Generated Content with TheChecker.AI

Free
View
D
Security

Detect GPT

Chrome extension that detects and flags AI-generated content

Free
View