Client tokens

Client tokens

An ephemeral, single‑call credential your backend mints and hands to an untrusted client — a browser, a mobile app, an Electron window — so that client can start one voice call without ever holding your API key.

Use this whenever a call starts on a device you don't control and you need it tied to a specific user, account, or context. If your page is fully public and anonymous, the public widget is simpler and needs no backend at all.

Why not just use the API key?

An API key is a bearer credential for your entire organization: every agent, every call record, every phone number. Anything shipped to a browser is public — devtools, view-source, a proxy, a determined user. A leaked key can dial out on your account until you notice and revoke it.

A client token inverts that. It is worth almost nothing to steal:

API keyClient token
ScopeWhole organizationOne agent
LifetimeUntil revokedMinutes (10 by default)
CallsUnlimited1 by default
Context variablesCaller choosesFixed at mint time
Safe in a browserNoYes

The flow

your server  ──X-API-Key──▶  POST /api/client-tokens          ──▶  { token, expiresAt }
     │
     └── token ──▶  browser  ──Bearer──▶  POST /api/sessions/init-web-call
                                                      │
                                                      ▼
                                          { sessionId, websocketUrl }  ──▶  connect

Your server decides who this caller is and what the agent should know. The browser only gets permission to start that one call.

Mint a token

POST /api/client-tokens

Auth: your org API key (role owner, admin, or developer) or a user JWT. Never call this endpoint from a browser — it's the thing that protects the key.

curl -X POST https://api.telenow.ai/api/client-tokens \
  -H "X-API-Key: vai_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "cce90897-5108-45bf-b849-693f0ec352b3",
    "ttlSeconds": 600,
    "maxCalls": 1,
    "variables": { "customerName": "Asha", "planTier": "gold" },
    "callerIdentity": { "identifier": "acct_8891", "channel": "web" }
  }'

Request

FieldTypeDefaultNotes
agentIduuidrequiredMust belong to your org, else 403. Validated at mint time so failures surface on your server, not in the browser.
ttlSecondsint600Clamped to 303600. Gates starting the call, not its duration — a call already underway isn't cut off when the token expires.
maxCallsint1Clamped to 1100. For retry/reconnect flows, not bulk dialing.
variablesobject{}String→string context variables baked into the token.
callerIdentityobjectTrusted caller identity (number, identifier, channel) injected into tool calls.

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "expiresAt": "2026-07-25T10:42:00Z"
}

Flat JSON — no {success, data} envelope.

Redeem a token

The client sends it as a Bearer token to init-web-call. The official browser SDK does this for you:

import { TelenowCall } from '@telenow/client-web';

const { token } = await fetch('/api/voice-token').then((r) => r.json()); // your endpoint
const call = new TelenowCall({ token });
await call.start();

Raw HTTP, if you're not using the SDK:

curl -X POST https://api.telenow.ai/api/sessions/init-web-call \
  -H "Authorization: Bearer eyJhbGciOi…" \
  -H "Content-Type: application/json" -d '{}'

The body can be empty: everything that matters is already in the token.

What the token pins

This is the security contract. At redemption these are read from the token, and anything the client sent in the request body is discarded:

  • agentId — a holder cannot switch to a different (or more expensive) agent.
  • variables — a holder cannot inject or overwrite prompt context. If you baked planTier: "gold", they cannot make it enterprise.
  • callerIdentity — cannot be forged, so tools can trust it for lookups.
  • userId — the session is attributed to the user whose key minted the token.
  • Softphone mode is refused outright (403) — a client token can never place an outbound PSTN call.

maxCalls is counted atomically server‑side, so two simultaneous redemptions can't both slip through on the last use.

Errors

StatusMeaningFix
401Invalid or expired client tokenPast expiresAt, malformed, or signed for a different environment. Mint a fresh one.
403This client token has already been used the maximum number of times.Raise maxCalls, or mint per call.
403A client token cannot start a softphone call.Use an API key server‑side for mode: "manual".
403Agent does not belong to this organizationAt mint time — check agentId.
404Agent not foundAt mint time — check agentId.

Practical guidance

  • Mint per call, on demand. Don't cache tokens; they're cheap. An endpoint like POST /api/voice-token on your own server, protected by your own session auth, is the whole integration.
  • Put your authorization in the minting endpoint. Telenow checks the token is valid; only you know whether this logged‑in user is allowed to call this agent with this account id. Decide that before you mint.
  • Keep TTLs short. The default 10 minutes covers a mic‑permission prompt and a slow connection. Longer just widens the window if a token leaks from a log or a shared screen.
  • Don't log tokens. They're credentials for their short life. Redact them like passwords.
  • ttlSeconds doesn't limit call length. For that, use the agent's own max‑duration setting.

See also