# User Identification

[Open in workspace →](/workspace/_/settings/messenger/install)

User identification lets you associate messenger conversations with known users in your application.

## Anonymous visitors

For visitors who aren't logged in, set the user to anonymous:

```js
optlo.setUser({ id: null });
```

Anonymous sessions are tracked via browser cookies.

## Identified users (without verification)

:::caution
Without verification, user identity can be faked from the browser. Only use this for testing or internal sites. Use [identity verification](#identified-users-with-verification) for production.
:::

Pass user data directly when JWT verification is not required:

```js
optlo.setUser({
  id: "user-123",
  email: "jane@example.com",
  name: "Jane Doe",
  avatarUrl: "https://example.com/avatar.jpg",
  attrs: {
    plan: "pro",
    active: true,
  },
  organizations: [{ id: "org-456", name: "Acme Corp", attrs: { industry: "SaaS" } }],
});
```

## User data fields

| Field           | Type                     | Required | Description                                |
| --------------- | ------------------------ | -------- | ------------------------------------------ |
| `id`            | string \| number \| null | yes      | Unique user ID, or `null` for anonymous    |
| `email`         | string                   | no       | Email address                              |
| `name`          | string                   | no       | Display name                               |
| `avatarUrl`     | string                   | no       | Profile image URL (must be `https://`)     |
| `attrs`         | object                   | no       | Custom key-value pairs (max 50)            |
| `organizations` | array                    | no       | Organizations the user belongs to (max 10) |

User and organization data appears in the [user details panel](/docs/inbox/user-details/) in the inbox.

## Organization fields

Each entry in the `organizations` array accepts the following fields:

| Field   | Type             | Required | Description                     |
| ------- | ---------------- | -------- | ------------------------------- |
| `id`    | string \| number | yes      | Unique organization identifier  |
| `name`  | string           | yes      | Organization display name       |
| `attrs` | object           | no       | Custom key-value pairs (max 50) |

Organizations are deduplicated by their `id`. A user can belong to up to 10 organizations.

The organizations array is always treated as the complete list. If you previously included an organization and omit it in a subsequent `setUser()` call, the user will be removed from that organization.

## Identified users (with verification)

Identity verification uses asymmetric cryptography — you sign user data on your server and we verify it on ours. This ensures user identity can't be faked from the browser. See [**Settings > Messenger > Install**](/workspace/_/settings/messenger/install) for a full walkthrough.

```js
optlo.setUser({ jwt: "eyJ..." });
```

### Generating the JWT

1. Generate an asymmetric key pair using one of the supported algorithms: `EdDSA`, `ES256`, `PS256`, or `RS256`. You can use `openssl` to generate a key pair:

   ```sh
   openssl genpkey -algorithm ed25519 -out private.pem
   openssl pkey -in private.pem -pubout -out public.pem
   ```

2. Upload the **public key** (SPKI format) in the [dashboard](/workspace/_/settings/messenger/install).
3. Sign a JWT on your server with the private key:

   ```js
   import { SignJWT } from "jose";

   let jwt = await new SignJWT({
     id: "user-123",
     email: "jane@example.com",
     name: "Jane Doe",
   })
     .setProtectedHeader({ alg: "EdDSA" })
     .setExpirationTime("5m")
     .sign(privateKey);
   ```

4. Pass the signed JWT to the widget from your frontend.

The JWT payload uses the same user data fields as the unsigned `setUser` call. The `exp` claim is required. You can optionally include a `jti` claim for one-time-use token enforcement.

When replacing a public key, the previous key remains valid for 2 hours to prevent disruption.
