# User tokens

> The JWT your server signs to tell Nexafon who is signed in, its claims, how users map to members, and how the SDK exchanges it for a session.

Source: https://docs.nexafone.so/v1/server/user-tokens/

A **user token** tells Nexafon which of your users is in front of the screen. Your server signs it; the SDK sends it
with your publishable key to `POST /api/v1/sdk/sessions` and gets an SDK session in return. The helpers sign it in
one call; this page is for understanding it, or for signing it in another language.

## Format

A JWT signed with **HS256**. The HMAC key is the full secret key string (`sk_live_…`/`sk_test_…`), of the **same
mode** as the publishable key.

| Claim | Required | |
| --- | --- | --- |
| `sub` | yes | Your id for the user, at most 255 characters. After the first sign-in Nexafon links it to the member |
| `jti` | yes | A new random id for every token; each token is accepted **once** |
| `exp` | yes | Expiry, at most **10 minutes** ahead (the helpers use 5) |
| `iat` | no | Issued at |
| `name` | no | The name shown in Nexafon; updates the member’s display name |
| `email` | first sign-in | Matches the user to the member with that email |
| `email_verified` | first sign-in | Must be `true` for Nexafon to trust `email` |
| `role` | no | Caps the session to this role’s permissions (`admin`, `supervisor`, `agent`, `viewer`) |

```json
{
  "sub": "user-42",
  "name": "Amina Ali",
  "email": "amina@yourcompany.com",
  "email_verified": true,
  "role": "agent",
  "iat": 1790000000,
  "exp": 1790000300,
  "jti": "3d1f0c2e-7a51-4c1e-9b8a-0d6c5e4f3a21"
}
```

## Which member the session acts as

1. A `sub` that signed in before maps to the same member again.
2. Otherwise the member with the token’s `email`, only when `email_verified` is `true`: an unverified email would let
   someone claim another member’s account.
3. Otherwise, if the workspace adds SDK users automatically, a new member with the token’s `role`, which may not be
   above the role the workspace allows for them. If not, the exchange answers `sdk_member_not_found`.

The workspace **owner** never signs in through the SDK (`sdk_role_not_allowed`): whoever holds a secret key could
otherwise act as the owner. A disabled member answers `sdk_member_disabled`.

## What the session may do

The session acts as that member, with at most:

- the member’s own role;
- the **scopes of the secret key** that signed the token, so give that key the permissions your embedded users need
  (see [security](/v1/security/#secret-key-scopes));
- the token’s `role`, when present.

## Lifetime

| | |
| --- | --- |
| User token | Up to 10 minutes; once |
| SDK session token (`sdk_…`) | 15 minutes; `POST /api/v1/sdk/sessions/refresh` returns a new one and ends the old |
| Refreshing | Up to 12 hours (`refreshUntil`); then the SDK asks `getUserToken` for a new user token |

Have `getUserToken` fetch a new token every time; the SDK calls it on start and when a session can no longer be
refreshed. `POST /api/v1/sdk/sessions/revoke` ends a session at once.

## Errors

| Code | Meaning |
| --- | --- |
| [`invalid_user_token`](/v1/errors/invalid_user_token/) | Not a valid HS256 JWT of a secret key of this workspace and mode, or a required claim is missing |
| [`user_token_expired`](/v1/errors/user_token_expired/) | `exp` passed, or more than 10 minutes ahead |
| [`user_token_replayed`](/v1/errors/user_token_replayed/) | The `jti` was used before |
| [`sdk_email_required`](/v1/errors/sdk_email_required/) | First sign-in without `email` |
| [`sdk_email_not_verified`](/v1/errors/sdk_email_not_verified/) | First sign-in without `email_verified: true` |
| [`sdk_member_not_found`](/v1/errors/sdk_member_not_found/) | No member with that email |
| [`client_not_allowed`](/v1/errors/client_not_allowed/) | The page’s origin is not allowed for the publishable key |

Partners sign a different token that lists stores: see [partner user tokens](/v1/partners/user-tokens/).
