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://ingest.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.

Next steps ​

Released under the MIT License.