Anonymous User Tracking: How Different Platforms Handle Identity Resolution
Compare how event tracking platforms handle anonymous users and identity linking. Learn why server-side anonymous ID generation with zero client configuration simplifies your analytics and provides better user journey insights.
One of the biggest challenges in event tracking is connecting anonymous user activity to identified users. When a visitor lands on your site, you want to track their behavior. When they sign up or log in, you want to link all that anonymous activity to their profile. This is called identity resolution, and different platforms handle it in vastly different ways.
The Identity Resolution Problem
Consider this common user journey:
- User visits your landing page (anonymous)
- User browses features, reads pricing (anonymous)
- User signs up with email (now identified)
- User makes a purchase (identified)
Without proper identity resolution, you see two separate users: an anonymous visitor and a registered user. You lose the complete picture of the user journey, making it impossible to:
- Attribute conversions to marketing channels
- Understand which content drives signups
- Calculate true customer acquisition costs
- Build accurate funnel analytics
Let’s examine how different platforms approach this problem.
Approach 1: Manual Anonymous ID Management
Used by: Segment, Amplitude, Mixpanel (partially)
The most common approach requires developers to manually generate, store, and manage anonymous IDs:
// You're responsible for generating and storing the ID
const getAnonymousId = () => {
let id = localStorage.getItem('anonymous_id');
if (!id) {
id = crypto.randomUUID();
localStorage.setItem('anonymous_id', id);
}
return id;
};
// Track anonymous event
analytics.track('Page Viewed', {
anonymous_id: getAnonymousId(),
});
// When user signs up, manually call identify
analytics.identify(userId, {
email: user.email,
name: user.name,
});
// Then alias the anonymous ID to the user ID
analytics.alias(userId, getAnonymousId());
Problems with Manual Management
- Boilerplate code: Every project needs the same ID generation logic
- Easy to forget: Developers must remember to include the anonymous ID in every event
- Alias complexity: The
identify+aliaspattern is error-prone - Storage inconsistency: Different storage mechanisms across platforms (localStorage, cookies, etc.)
- Server-side gaps: Anonymous IDs from the client don’t automatically flow to server events
Approach 2: Separate Identify Calls
Used by: Mixpanel, Heap
Some platforms require explicit identify() calls to link anonymous and identified users:
// Mixpanel approach
mixpanel.track('Page Viewed'); // Uses Mixpanel's internal distinct_id
// When user logs in
mixpanel.identify(userId);
// All future events are linked to userId
// But you need to manually merge anonymous events
mixpanel.alias(userId); // Creates alias from anonymous to identified
The Alias Problem
The alias() function is one of the most confusing concepts in analytics:
- When to call it: Only once per user, typically at signup (not login)
- Direction matters: Alias the new ID to the old ID, not vice versa
- Irreversible: Once aliased, you can’t undo it
- Timing issues: If called at the wrong time, you lose data or create duplicate profiles
This complexity leads to common mistakes:
// Common mistake: Calling alias on every login
const handleLogin = (user) => {
mixpanel.identify(user.id);
mixpanel.alias(user.id); // Wrong! Creates issues for returning users
};
// Correct: Only alias on first identification (signup)
const handleSignup = (user) => {
mixpanel.alias(user.id); // Link anonymous to new user
mixpanel.identify(user.id);
};
const handleLogin = (user) => {
mixpanel.identify(user.id); // Just identify, no alias
};
Approach 3: Cookie-Based Tracking
Used by: Google Analytics, Amplitude
These platforms use cookies or device IDs to track users automatically:
// Google Analytics 4
gtag('event', 'page_view'); // Automatically uses client_id from cookie
// Linking to user ID requires explicit set
gtag('config', 'GA_MEASUREMENT_ID', {
user_id: 'USER_ID',
});
Cookie Limitations
- Privacy regulations: GDPR, CCPA require cookie consent
- Cross-device gaps: Cookies don’t follow users across devices
- Browser restrictions: Safari ITP, Firefox ETP limit cookie lifetime
- Ad blockers: Many users block tracking cookies entirely
Approach 4: Server-Side Anonymous ID with Zero Client Configuration (Kitbase)
Kitbase takes a fundamentally different approach: the backend generates anonymous IDs automatically from request context, so the SDK never needs to create, store, or send an anonymous ID at all.
import { Kitbase } from '@kitbase/analytics';
const kitbase = new Kitbase({
token: 'your-api-key',
});
// Just track events -- no anonymous ID concerns whatsoever
await kitbase.track({
channel: 'marketing',
event: 'Page Viewed',
});
// When user signs up/logs in, just call identify with user_id
await kitbase.identify({
user_id: 'user_123',
traits: {
email: 'user@example.com',
name: 'Jane Doe',
},
});
There is no anonymous ID in your code. No ID generation. No storage. The backend handles everything.
How Kitbase Identity Resolution Works
- Server-side ID generation: When an event arrives, the backend generates an
anonymous_idby computing a SHA-256 hash of the User-Agent, IP address, project ID, and a daily-rotating salt. This produces a stable identifier for the same visitor across requests within a given day. - No client-side storage needed: Because the ID is derived from request context, there is nothing to store in localStorage, cookies, or memory. The SDK is completely stateless with respect to identity.
- Daily salt rotation for privacy: The salt used in the hash rotates every day. This means the raw hash naturally expires, limiting long-term tracking while still giving you accurate intra-day and short-term journey data.
- Automatic backfill on identify: When you call
identify()with auser_id, the backend already knows the caller’s anonymous ID from the request context. It retroactively backfills theuser_idon all past events that share the same anonymous ID, stitching together the full user journey. - No alias needed: There is no alias step. The linking between anonymous and identified activity happens entirely on the server, automatically.
Server-Side Identity Resolution
When Kitbase receives an event, the backend derives the anonymous ID from the request itself. Here is what the server sees after processing a track call:
{
"channel": "users",
"event": "User Signed Up",
"user_id": "user_123",
"timestamp": "2025-01-07T10:30:00Z"
}
The SDK sends only the event data. The backend then:
- Derives the anonymous ID from the request context (User-Agent + IP + project ID + salt)
- Attaches the anonymous ID to the event internally
- If a
user_idis present, retroactively attributes all previous anonymous events with that same anonymous ID to this user - Stores the linked identity for future cross-device and cross-session resolution
The Code Difference
Compare the complexity:
Traditional approach (Segment-style):
// Setup
const anonymousId = localStorage.getItem('ajs_anonymous_id')
|| (localStorage.setItem('ajs_anonymous_id', uuid()), uuid());
// Every track call
analytics.track('Event Name', { ...props }, { anonymousId });
// On signup (only!)
analytics.alias(userId, anonymousId);
analytics.identify(userId, traits);
// On login (different from signup!)
analytics.identify(userId, traits);
Kitbase approach:
// Setup
const kitbase = new Kitbase({ token: 'your-api-key' });
// Every track call -- nothing else needed
await kitbase.track({ channel: 'marketing', event: 'Event Name', ...props });
// On signup OR login -- just provide user_id
await kitbase.identify({ user_id: userId });
No manual ID management. No alias calls. No signup vs. login distinction. No localStorage. No cookies.
Real-World Scenarios
Scenario 1: Marketing Attribution
Goal: Know which landing page led to a signup
Traditional approach:
// Landing page
analytics.track('Landing Page Viewed', {
page: 'pricing',
anonymousId: getAnonymousId(),
});
// Signup page - must remember to alias
analytics.alias(newUserId, getAnonymousId());
analytics.identify(newUserId);
analytics.track('Signed Up');
// Query: Join anonymous events to identified events via alias table
// Complex SQL with multiple joins required
Kitbase approach:
// Landing page
await kitbase.track({
channel: 'marketing',
event: 'Landing Page Viewed',
tags: { page: 'pricing' },
});
// Signup page - just identify the user
await kitbase.identify({ user_id: newUserId });
await kitbase.track({
channel: 'users',
event: 'Signed Up',
user_id: newUserId,
});
// Query: Filter by user_id - anonymous events already attributed
Scenario 2: Cross-Device Journey
User browses on mobile, purchases on desktop.
Traditional approach: Requires complex device graph and probabilistic matching.
Kitbase approach: When user logs in on desktop, the backend derives their anonymous ID from the new device context, and identify() links it to their user_id. All previous anonymous events from any device where they identified are already attributed.
Scenario 3: Returning Anonymous User
User visits, leaves, returns a week later, then signs up.
Traditional approach: Hope the cookie/localStorage survived. If not, you have two anonymous profiles.
Kitbase approach: Because the anonymous ID is derived server-side from request signals, there is no reliance on client-side storage surviving between visits. As long as the visitor’s request context (device, network) is consistent, they receive the same anonymous ID. When they sign up, the backend backfills all matching anonymous events to their profile.
Session Management
Kitbase also handles session management server-side via Redis with a 30-minute TTL. There is nothing to configure on the client:
- Sessions are created and extended automatically on the backend as events arrive
- A session expires after 30 minutes of inactivity
- Session data is associated with the anonymous ID, so it carries over seamlessly when a user identifies
This eliminates another category of client-side complexity that other platforms push onto developers.
Privacy Considerations
Kitbase’s server-side approach is designed with privacy in mind:
- No PII in the hash: The anonymous ID is a one-way SHA-256 hash. It cannot be reversed to recover the original IP address, User-Agent, or any other input.
- Daily salt rotation: The hash salt rotates every day, which means the anonymous ID naturally changes over time. This limits long-term tracking by design.
- No client-side storage: Because no anonymous ID is stored in the browser, there are no cookies or localStorage entries for users (or regulators) to worry about.
- First-party only: All computation happens on your Kitbase backend. No third-party cookies, no cross-site tracking.
- Consent-friendly: Since no persistent client-side identifiers are used, the compliance surface area is significantly smaller.
Comparison Summary
| Feature | Manual ID | Alias-Based | Cookie-Based | Kitbase |
|---|---|---|---|---|
| Setup complexity | High | Medium | Low | None |
| Developer burden | High | High | Medium | None |
| Client-side storage | Required | Required | Required | Not needed |
| Alias calls needed | Yes | Yes | Sometimes | No |
| Automatic linking | No | No | Partial | Yes |
| Cross-device support | Manual | Manual | Limited | Via user_id |
| Privacy-friendly | Depends | Depends | No (cookies) | Yes (hashed, no PII) |
| Server-side support | Manual | Manual | Limited | Automatic |
Getting Started
Implementing proper identity resolution with Kitbase takes minutes:
import { Kitbase } from '@kitbase/analytics';
// Initialize once
const kitbase = new Kitbase({
token: 'your-api-key',
});
// Track anonymous events -- identity is handled server-side
await kitbase.track({
channel: 'marketing',
event: 'Page Viewed',
tags: { page: window.location.pathname },
});
// When user authenticates, call identify
// The backend links all past anonymous events to this user
await kitbase.identify({ user_id: user.id });
// Future track calls can include user_id directly
await kitbase.track({
channel: 'users',
event: 'User Authenticated',
user_id: user.id,
});
That’s it. No ID generation code. No alias calls. No storage configuration. No cookies. Just track events and call identify() when you know who the user is.
Conclusion
Identity resolution doesn’t have to be complicated. While traditional platforms require manual ID management, alias calls, client-side storage, and careful orchestration between signup and login flows, Kitbase handles all of it on the server.
Kitbase’s approach — server-side anonymous ID generation with automatic backfill on identify — eliminates an entire category of analytics bugs while providing complete user journey visibility. There is nothing to configure, nothing to store, and nothing to get wrong on the client.
The result: less code, fewer bugs, and better data.
Ready to simplify your user tracking? Try Kitbase free and see identity resolution that just works.