Agent skill
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 applications, and privacy-conscious implementations.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/design/fullstory-anonymize-users
SKILL.md
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
- Current session is closed and marked as belonging to the identified user
- A new
fs_uidcookie is generated - breaking the link to all previous sessions - New anonymous session begins with a new session ID and new cookie
- Previous user data is preserved - anonymizing doesn't delete history
- Subsequent activity is anonymous until a new
setIdentitycall
Cookie Behavior: Normally, the
fs_uidfirst-party cookie (1-year expiry) links all sessions from the same browser together. Whenanonymizeis called, Fullstory generates a newfs_uidcookie, effectively creating a "new device" from Fullstory's perspective. Any futuresetIdentitycalls 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
| Scenario | Should Anonymize? | Reason |
|---|---|---|
| User logs out | ✅ Yes | Prevents session attribution to wrong user |
| User switches accounts | ✅ Yes | Clean slate before new identification |
| User requests data deletion | ❓ Consider | Part of broader privacy implementation |
| User clears browser data | ❌ No | Fullstory handles this automatically |
| Page navigation | ❌ No | Identity persists across pages |
| Session timeout | ❓ Depends | Based on your security requirements |
API Reference
Basic Syntax
FS('setIdentity', { anonymous: true });
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
anonymous |
boolean | Yes | Must 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:
- ❌ Anonymize called after page navigation starts
- ❌ Anonymize never called (missing from logout flow)
- ❌ 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:
- ❌ Calling anonymize on every page load
- ❌ Calling anonymize on errors
- ❌ 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:
-
Always emphasize:
- Anonymize BEFORE navigation/redirects
- Use
{ anonymous: true }syntax exactly - Track important events BEFORE anonymizing
- Single call per logout is sufficient
-
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
-
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?
-
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
- Anonymize Users: https://developer.fullstory.com/browser/identification/anonymize-users/
- Identify Users: https://developer.fullstory.com/browser/identification/identify-users/
- Help Center - Anonymizing: https://help.fullstory.com/hc/en-us/articles/360020623514-Anonymizing-Users
This skill document was created to help Agent understand and guide developers in implementing Fullstory's User Anonymization API correctly for web applications.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?