---
url: 'https://docs.kitbase.dev/identify-users.md'
description: >-
  Learn how to identify users in Kitbase, link anonymous activity to real users,
  and manage identity across devices.
---

# 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:

| Identifier | Description |
|------------|-------------|
| `anonymous_id` | Auto-generated UUID assigned to every visitor before they log in |
| `user_id` | Your 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'
  }
});
```

::: warning 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
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | `string` | Yes | Your unique user identifier (database ID recommended) |
| `traits` | `Record<string, unknown>` | No | Arbitrary key-value pairs describing the user |

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

| Trait | Example |
|-------|---------|
| `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
    }
  });
}
```

::: tip 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
```

::: tip 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
}
```

| Field | Type | Description |
|-------|------|-------------|
| `success` | `boolean` | Whether the identification succeeded |
| `userId` | `string` | The identified user ID |
| `previouslyLinked` | `boolean` | `true` 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

* [Users dashboard guide](/guide/users) — profiles, traits, and identity resolution in the dashboard.
* [Track Events](/track-events) — send custom events for your identified users.
* [JavaScript SDK identity API](/sdks/javascript#user-identity) — `identify()`, `getAnonymousId()`, `reset()`.
