Skip to main content

Authentication

The 2Chat Voice SDK never sees your long-lived X-User-API-Key. Instead, your backend exchanges that key for a short-lived JWT scoped to a single 2Chat User, and that JWT is what the browser holds.

Why this matters

  • Your X-User-API-Key stays on a trusted server.
  • Each browser tab is scoped to one User UUID — perfect for multi-agent softphones.
  • Tokens expire (default 1 hour, max 24 hours) so a leaked JWT is short-lived.
  • You can rotate tokens transparently with device.updateToken(...) — no re-registration.

Mint an access token

Endpoint

POST https://api.p.2chat.io/sdk/voip/access-token

Authentication

Server-to-server only. Send your account's X-User-API-Keynever ship this to the browser.

Parameters

NameTypeRequiredDescription
user_uuidstringyesUUID of the 2Chat User this token represents (USR…). Each token binds to one user. Discover UUIDs via GET /open/users.
labelstringnoFree-form label echoed back in the token claims (e.g. agent-ada, softphone-tab-1). Useful for telemetry.
ttlintegernoLifetime in seconds. Clamped to [60, 86400]. Default 3600 (1 hour).

Invocation

curl --location --request POST 'https://api.p.2chat.io/sdk/voip/access-token' \
--header 'X-User-API-Key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_uuid": "USR48a1c2f0-9d6b-4c2a-8e3f-1a7b9d0c4e22",
"label": "agent-ada",
"ttl": 1800
}'

Response

{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_uuid": "USR48a1c2f0-9d6b-4c2a-8e3f-1a7b9d0c4e22",
"label": "agent-ada",
"expires_at": "2026-05-08T14:30:00+00:00"
}
FieldTypeDescription
successbooleantrue on success.
tokenstringThe signed JWT to hand to the browser.
user_uuidstringThe User UUID the token is bound to.
labelstring | nullThe label you passed, echoed back.
expires_atstring (ISO 8601)Absolute expiry time of the token (UTC).

Errors follow the standard API error format. A missing or unknown user_uuid returns 400 / 404; a bad API key returns 401.

Refresh strategy

The SDK never refreshes tokens on its own — your backend is the source of truth. The recommended pattern:

device.on("tokenWillExpire", async () => {
const { token } = await fetch("/my/backend/voice-token").then((r) => r.json());
await device.updateToken(token);
});

updateToken() swaps the token in place. The new token must be for the same user_uuid — switching users requires a new Device.

Adjust the warning window with tokenExpiryLeadMs on DeviceOptions (default 60_000 ms).

Multiple agents, multiple tabs

Each JWT is bound to one 2Chat User. To run N concurrent agents:

  • Provision N Users in your 2Chat account.
  • Mint one token per user per session.
  • Spin up one Device per token in its own browser context.

A single User cannot be registered from two browsers at the same time — the second register replaces the first.

Security checklist

  • ✅ Mint tokens on the server, never the browser.
  • ✅ Store X-User-API-Key in environment variables / secrets manager.
  • ✅ Treat the JWT like any short-lived bearer token — HTTPS only.
  • ✅ Use the shortest reasonable TTL (ttl) for your use case.
  • ❌ Don't embed X-User-API-Key in client-side JavaScript.
  • ❌ Don't reuse one token across multiple users — claims and SIP identity won't match.