> ## Documentation Index
> Fetch the complete documentation index at: https://retain.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Identify user

> Register or update users in Retain

When a user signs up or updates their profile, send their data to Retain. You must identify a user before tracking events for them.

<Note>
  Call `/identify` before `/track`. Events can only be recorded for users that
  have been identified.
</Note>

## Identify a user

<CodeGroup>
  ```typescript Node.js icon=square-js theme={"theme":"vesper"}
  import { Retain } from '@retain-so/sdk';

  const retain = new Retain(process.env.RETAIN_WRITE_KEY);

  retain.identify({
    userId: 'user_123',
    email: 'jane@example.com',
    properties: {
      name: 'Jane Doe',
      avatar: 'https://example.com/avatar.jpg',
      // Add anything else about the user here
    },
  });
  ```

  ```python Python icon=python theme={"theme":"vesper"}
  import os
  import requests

  requests.post(
      'https://api.retain.so/identify',
      headers={
          'Content-Type': 'application/json',
          'Authorization': f'Basic {os.environ["RETAIN_WRITE_KEY"]}',
      },
      json={
          'userId': 'user_123',
          'email': 'jane@example.com',
          'properties': {
              'name': 'Jane Doe',
              'avatar': 'https://example.com/avatar.jpg',
              # Add anything else about the user here
          },
      },
  )
  ```

  ```php PHP icon=php theme={"theme":"vesper"}
  $payload = json_encode([
      'userId' => 'user_123',
      'email' => 'jane@example.com',
      'properties' => [
          'name' => 'Jane Doe',
          'avatar' => 'https://example.com/avatar.jpg',
      ],
  ]);

  $ch = curl_init('https://api.retain.so/identify');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Content-Type: application/json',
          'Authorization: Basic ' . getenv('RETAIN_WRITE_KEY'),
      ],
      CURLOPT_POSTFIELDS => $payload,
  ]);
  curl_exec($ch);
  curl_close($ch);
  ```

  ```ruby Ruby icon=gem theme={"theme":"vesper"}
  require 'net/http'
  require 'json'

  uri = URI('https://api.retain.so/identify')
  req = Net::HTTP::Post.new(uri)
  req['Content-Type'] = 'application/json'
  req['Authorization'] = "Basic #{ENV.fetch('RETAIN_WRITE_KEY')}"
  req.body = {
    userId: 'user_123',
    email: 'jane@example.com',
    properties: {
      name: 'Jane Doe',
      avatar: 'https://example.com/avatar.jpg',
      # Add anything else about the user here
    },
  }.to_json

  Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  ```

  ```java Java icon=java theme={"theme":"vesper"}
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  String json = """
    {
      "userId": "user_123",
      "email": "jane@example.com",
      "properties": {
        "name": "Jane Doe",
        "avatar": "https://example.com/avatar.jpg"
      }
    }
    """;

  HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.retain.so/identify"))
      .header("Content-Type", "application/json")
      .header("Authorization", "Basic " + System.getenv("RETAIN_WRITE_KEY"))
      .POST(HttpRequest.BodyPublishers.ofString(json))
      .build();

  HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
  ```

  ```bash cURL icon=globe theme={"theme":"vesper"}
  curl -X POST https://api.retain.so/identify \
    -H "Content-Type: application/json" \
    -H "Authorization: Basic $RETAIN_WRITE_KEY" \
    -d '{
      "userId": "user_123",
      "email": "jane@example.com",
      "properties": {
        "name": "Jane Doe",
        "avatar": "https://example.com/avatar.jpg"
      }
    }'
  ```
</CodeGroup>

## Identify a user with account

For products with workspaces or teams, you can associate users with an account and mark the account owner:

<CodeGroup>
  ```typescript Node.js icon=square-js theme={"theme":"vesper"}
  import { Retain } from '@retain-so/sdk';

  const retain = new Retain(process.env.RETAIN_WRITE_KEY);

  retain.identify({
    userId: 'user_123',
    email: 'jane@example.com',
    isAccountOwner: true,
    account: {
      id: 'acct_456',
      name: 'Acme Inc',
      avatar: 'https://example.com/acme-logo.png',
    },
    properties: {
      name: 'Jane Doe',
      avatar: 'https://example.com/avatar.jpg',
    },
  });
  ```

  ```python Python icon=python theme={"theme":"vesper"}
  import os
  import requests

  requests.post(
      'https://api.retain.so/identify',
      headers={
          'Content-Type': 'application/json',
          'Authorization': f'Basic {os.environ["RETAIN_WRITE_KEY"]}',
      },
      json={
          'userId': 'user_123',
          'email': 'jane@example.com',
          'isAccountOwner': True,
          'account': {
              'id': 'acct_456',
              'name': 'Acme Inc',
              'avatar': 'https://example.com/acme-logo.png',
          },
          'properties': {
              'name': 'Jane Doe',
              'avatar': 'https://example.com/avatar.jpg',
          },
      },
  )
  ```

  ```php PHP icon=php theme={"theme":"vesper"}
  $payload = json_encode([
      'userId' => 'user_123',
      'email' => 'jane@example.com',
      'isAccountOwner' => true,
      'account' => [
          'id' => 'acct_456',
          'name' => 'Acme Inc',
          'avatar' => 'https://example.com/acme-logo.png',
      ],
      'properties' => [
          'name' => 'Jane Doe',
          'avatar' => 'https://example.com/avatar.jpg',
      ],
  ]);

  $ch = curl_init('https://api.retain.so/identify');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Content-Type: application/json',
          'Authorization: Basic ' . getenv('RETAIN_WRITE_KEY'),
      ],
      CURLOPT_POSTFIELDS => $payload,
  ]);
  curl_exec($ch);
  curl_close($ch);
  ```

  ```ruby Ruby icon=gem theme={"theme":"vesper"}
  require 'net/http'
  require 'json'

  uri = URI('https://api.retain.so/identify')
  req = Net::HTTP::Post.new(uri)
  req['Content-Type'] = 'application/json'
  req['Authorization'] = "Basic #{ENV.fetch('RETAIN_WRITE_KEY')}"
  req.body = {
    userId: 'user_123',
    email: 'jane@example.com',
    isAccountOwner: true,
    account: {
      id: 'acct_456',
      name: 'Acme Inc',
      avatar: 'https://example.com/acme-logo.png',
    },
    properties: {
      name: 'Jane Doe',
      avatar: 'https://example.com/avatar.jpg',
    },
  }.to_json

  Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  ```

  ```java Java icon=java theme={"theme":"vesper"}
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  String json = """
    {
      "userId": "user_123",
      "email": "jane@example.com",
      "isAccountOwner": true,
      "account": {
        "id": "acct_456",
        "name": "Acme Inc",
        "avatar": "https://example.com/acme-logo.png"
      },
      "properties": {
        "name": "Jane Doe",
        "avatar": "https://example.com/avatar.jpg"
      }
    }
    """;

  HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.retain.so/identify"))
      .header("Content-Type", "application/json")
      .header("Authorization", "Basic " + System.getenv("RETAIN_WRITE_KEY"))
      .POST(HttpRequest.BodyPublishers.ofString(json))
      .build();

  HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
  ```

  ```bash cURL theme={"theme":"vesper"}
  curl -X POST https://api.retain.so/identify \
    -H "Content-Type: application/json" \
    -H "Authorization: Basic $RETAIN_WRITE_KEY" \
    -d '{
      "userId": "user_123",
      "email": "jane@example.com",
      "isAccountOwner": true,
      "account": {
        "id": "acct_456",
        "name": "Acme Inc",
        "avatar": "https://example.com/acme-logo.png"
      },
      "properties": {
        "name": "Jane Doe",
        "avatar": "https://example.com/avatar.jpg"
      }
    }'
  ```
</CodeGroup>

See [POST /identify](/docs/api-reference/identify/identify-a-user) in the API Reference for more details.

## Reserved traits

Everything you send in `properties` is stored as a trait on the customer. A few keys are reserved and power specific features in the dashboard:

| Key                  | Type         | What it powers                                                                       |
| -------------------- | ------------ | ------------------------------------------------------------------------------------ |
| `name`               | string       | Display name shown across the dashboard                                              |
| `avatar`             | string (URL) | Customer avatar shown across the dashboard                                           |
| `plan`               | string       | Retention cohorts split by pricing plan (e.g. `starter`, `pro`)                      |
| `acquisitionChannel` | string       | Retention cohorts split by where the customer came from (e.g. `organic`, `referral`) |
| `companySize`        | string       | Retention cohorts split by company size bucket (e.g. `1-10`, `11-50`)                |
| `segment`            | string       | Retention cohorts split by the company's ICP segment (e.g. `agency`, `creator`)      |

<Note>
  The `segment` trait is the segment your customer belongs to in your ICP. It
  is not the behavioral segment Retain calculates for each account (Curious,
  Loyal, Zombie, and friends). Those are computed from usage, not sent by you.
</Note>

Any other key is stored as a custom trait. Custom traits with a small set of values (20 or fewer) also show up as cohort grouping options in the Metrics page.

### Traits are merged, not replaced

Each `identify` call does a shallow merge: keys you send overwrite the same keys, keys you omit are preserved. You can send `plan` on signup and `acquisitionChannel` later without losing anything. Send `null` for a key to clear it.

### Split retention cohorts by trait

Send the cohort traits and the Metrics page lets you split the retention chart by any of them. That is how you find out which plan, channel, or segment quietly churns the most:

<CodeGroup>
  ```typescript Node.js icon=square-js theme={"theme":"vesper"}
  retain.identify({
    userId: 'user_123',
    email: 'jane@example.com',
    properties: {
      plan: 'starter',
      acquisitionChannel: 'organic',
      companySize: '11-50',
      segment: 'agency',
    },
  });
  ```

  ```bash cURL theme={"theme":"vesper"}
  curl -X POST https://api.retain.so/identify \
    -H "Content-Type: application/json" \
    -H "Authorization: Basic $RETAIN_WRITE_KEY" \
    -d '{
      "userId": "user_123",
      "email": "jane@example.com",
      "properties": {
        "plan": "starter",
        "acquisitionChannel": "organic",
        "companySize": "11-50",
        "segment": "agency"
      }
    }'
  ```
</CodeGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Track" icon="chart-line" href="/docs/guides/track">
    Record events for identified users.
  </Card>
</CardGroup>
