Skip to content

Identify Users

Link anonymous visitors to known users so every event, from first page view to final purchase, is attributed to a single person.

Overview

Kitbase tracks users with two identifiers:

IdentifierDescription
anonymous_idAuto-generated UUID assigned to every visitor before they log in
user_idYour unique user identifier, set when you call identify()

Before identification, all events are linked to the auto-generated anonymous ID. After identify() is called, the SDK sends a request to POST /sdk/v1/identify (authenticated with your SDK key), and subsequent events are attributed to the real user. The server also backfills past anonymous events so nothing is lost.

How It Works

1. User visits site     → anonymous_id generated (UUID, stored in localStorage)
2. User browses         → events tracked with anonymous_id only
3. User logs in         → identify(userId, traits) called
4. SDK sends request    → server links anonymous_id to user_id
5. Server backfills     → previous anonymous events now attributed to user
6. Subsequent events    → include both anonymous_id and user_id

Basic Usage

Call identify() immediately after the user authenticates:

typescript
// After user logs in
await kitbase.identify({
  userId: 'user_123',
  traits: {
    email: 'jane@example.com',
    name: 'Jane Doe',
    plan: 'premium',
    company: 'Acme Inc'
  }
});

Identity Linking Requires identify()

Kitbase does not automatically link anonymous users to identified users. You must explicitly call identify() after authentication.

Identify Options

typescript
interface IdentifyOptions {
  userId: string;                       // Required: your unique user identifier
  traits?: Record<string, unknown>;     // Optional: user properties
}
ParameterTypeRequiredDescription
userIdstringYesYour unique user identifier (database ID recommended)
traitsRecord<string, unknown>NoArbitrary key-value pairs describing the user

Traits can include any key-value pairs. Common examples:

TraitExample
email"jane@example.com"
name"Jane Doe"
plan"premium"
company"Acme Inc"
role"admin"
created_at"2025-03-15T10:00:00Z"

When to Call identify()

Call identify() immediately after any of these events:

  • After login (email/password, OAuth, magic link)
  • After signup (new account creation)
  • After OAuth callback (Google, GitHub, etc.)
  • On app load when the user is already authenticated (check your auth state)
typescript
// After OAuth login
async function handleOAuthCallback(user) {
  await kitbase.identify({
    userId: user.id,
    traits: {
      email: user.email,
      name: user.name,
      provider: 'google'
    }
  });
}

// On app load if already authenticated
if (currentUser) {
  await kitbase.identify({
    userId: currentUser.id,
    traits: {
      email: currentUser.email,
      plan: currentUser.plan
    }
  });
}

Call identify() as early as possible

The sooner you identify a user after authentication, the fewer events remain anonymous. Place your identify() call at the earliest point in your auth flow.

Anonymous ID Management

Retrieve the current identifiers at any time:

typescript
kitbase.getAnonymousId();  // Returns current anonymous UUID
kitbase.getUserId();       // Returns user_id if identified, null otherwise

Storage Options

The anonymous ID is persisted across page reloads. You can configure how and where it is stored:

typescript
// Default: localStorage
const kitbase = init({
  sdkKey: '<YOUR_SDK_KEY>',
  storage: localStorage
});

// Custom storage (must implement getItem / setItem / removeItem)
const kitbase = init({
  sdkKey: '<YOUR_SDK_KEY>',
  storage: myCustomStorage
});

// No persistence (new anonymous ID generated each session)
const kitbase = init({
  sdkKey: '<YOUR_SDK_KEY>',
  storage: null
});

Reset Identity

Clear the current user and start a fresh anonymous session:

typescript
kitbase.reset();
// Clears user_id, generates a new anonymous_id, starts a new session

Use reset() when:

  • The user logs out
  • The user switches accounts
typescript
async function handleLogout() {
  await authService.logout();
  kitbase.reset(); // Start tracking as a new anonymous user
}

Multi-Device Support

When the same person uses your app on multiple devices:

  • Each device generates its own anonymous_id
  • Calling identify() with the same userId on each device links all activity to one user
  • Events from every device are attributed to the same user profile
typescript
// Device A: anonymous_id = "aaa-111"
await kitbase.identify({ userId: 'user_123' });
// -> aaa-111 linked to user_123

// Device B: anonymous_id = "bbb-222"
await kitbase.identify({ userId: 'user_123' });
// -> bbb-222 also linked to user_123

// All events from both devices are now attributed to user_123

MAU Accuracy

Identity resolution prevents double-counting users who browse anonymously before signing in. A user who visits your site anonymously and later creates an account counts as one MAU, not two.

Server-Side Behavior

When identify() is called, the following happens on the server:

  1. The SDK sends POST /sdk/v1/identify with { userId, anonymousId, traits }
  2. The server creates a link between the anonymous_id and the user_id
  3. The server backfills user_id on all past events matching that anonymous_id in ClickHouse
  4. The server responds with:
json
{
  "success": true,
  "userId": "user_123",
  "previouslyLinked": false
}
FieldTypeDescription
successbooleanWhether the identification succeeded
userIdstringThe identified user ID
previouslyLinkedbooleantrue if this anonymous ID was already linked to this user

REST API (Direct)

You can call the identify endpoint directly without the SDK:

bash
curl -X POST https://api.kitbase.dev/sdk/v1/identify \
  -H "x-sdk-key: YOUR_SDK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_123",
    "anonymous_id": "550e8400-e29b-41d4-a716-446655440000",
    "traits": {
      "email": "jane@example.com",
      "name": "Jane Doe"
    }
  }'

Best Practices

Use consistent user IDs

Always use the same identifier format for a given user. Database IDs or UUIDs work well:

typescript
// Good: consistent database ID
await kitbase.identify({ userId: user.id });

// Bad: mixing formats across your app
await kitbase.identify({ userId: user.email });   // email in one place
await kitbase.identify({ userId: String(user.id) }); // numeric ID elsewhere

Avoid PII as user IDs

Do not use personally identifiable information (email addresses, phone numbers) as the userId. Use opaque database IDs instead, and pass PII as traits:

typescript
// Good
await kitbase.identify({
  userId: 'usr_8f3a2b',
  traits: { email: 'jane@example.com' }
});

// Bad
await kitbase.identify({
  userId: 'jane@example.com'
});

Update traits when the profile changes

Call identify() again whenever user properties change (plan upgrade, name update, etc.) to keep traits current:

typescript
async function handlePlanUpgrade(newPlan: string) {
  await billingService.upgrade(newPlan);

  await kitbase.identify({
    userId: currentUser.id,
    traits: {
      plan: newPlan,
      upgraded_at: new Date().toISOString()
    }
  });
}

Call reset() on logout

Always call reset() when a user logs out to prevent the next user on a shared device from inheriting the previous identity:

typescript
async function handleLogout() {
  await authService.logout();
  kitbase.reset();
}

Identify as early as possible

Place your identify() call at the earliest point after authentication. The longer you wait, the more events are tracked without a user_id.

Released under the MIT License.