Token rotation

There are two kinds of credentials, and they rotate differently:

  • API keys (pk_live_…) live on your backend until you revoke them.
  • End-user JWTs live for one hour. Your backend mints a new one each time the SDK asks.

Rotating an API key

Rotate on a schedule, and at once if a key leaks. You can have several active keys, so there is no downtime.

1. Create the new key

On the dashboard, open API keys and create a key.

  • Give it the same scopes as the old key, or fewer. A key that mints user tokens needs users:manage.
  • Put the date in the name so you can tell the keys apart.
  • Copy the full key; it is shown once.

See API keys.

2. Deploy it

Put the new key in your backend's secret store (for example the POOLSE_API_KEY environment variable) and deploy. From now on both keys work.

3. Check that the old key is unused

The Last used column on the API keys page shows when each key last made a request. Wait until the new key shows activity and the old key's time stops moving.

4. Revoke the old key

Click Revoke on the old key. The check happens on every request, so the next request with that key fails with 401 revoked_api_key.

Tokens minted with the old key

Revoking an API key doesn't invalidate JWTs it already minted. They keep working until they expire (at most one hour), because the server checks a JWT's signature and expiry, not the key that minted it.

To end a specific JWT earlier, revoke it.

Revoking an end-user JWT

POST/admin/v1/tokens/revoke
API keytokens:admin
curl -X POST https://api.poolse.dev/admin/v1/tokens/revoke \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJhbGciOi…"}'
{ "jti": "9b3c…", "expires_at": "2026-10-27T15:42:10Z" }

The token is blocked for the rest of its lifetime. A blocked token is refused on REST requests with 401 token_revoked, and when it is used to open the socket.

Revoking also closes every open socket of that user, because a socket doesn't record which token opened it. A device that holds a different, valid token reconnects on its own, and from 3.1 useMessages and useConversations reload what was sent in the gap (see Reconnects). On a device whose token was the revoked one, the socket handshake is refused; from 3.1 the SDK then asks your getToken for a new token (at most every 30 seconds) and reconnects with it, so if your route still issues tokens for that user the device recovers on its own. Its next REST call does the same after the 401.

StatusCodeWhen
400missing_tokenThe body has no token field.
400invalid_tokenThe token is malformed, already expired, or lacks the required claims.
403forbiddenThe key lacks the tokens:admin scope.
403cross_tenantThe token belongs to another workspace.

Revoking a token doesn't stop your backend from minting a new one for the same user. To keep someone out for good, block them.

Blocking a user

Set is_blocked with the user upsert:

curl -X POST https://api.poolse.dev/v1/users \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"user_123","is_blocked":true}'

While a user is blocked:

  • Every REST request with their token fails with 401 user_blocked, including tokens minted before the block.
  • Opening the socket is refused.
  • Their open sockets are closed when the block is saved.
  • Every call action is refused: starting, ringing or joining calls and rooms, and answering, declining, hanging up or ringing people in with a call's own grant (the token the phone gets for a ringing or live call). This includes grants issued before the block. Those requests get 401 unauthorized with the message "User is blocked.".
  • POST /v1/users/:id/tokens still mints a token for them. The token is refused as soon as it is used, but your token route should stop asking for one anyway.

To unblock, send the upsert again with "is_blocked": false. Leaving the field out keeps the current value.

Rotating end-user JWTs

You don't need to. They expire after an hour, and the SDK asks your getToken for a new one shortly before that (see Token caching). Make sure your token route mints a new JWT on each call instead of returning a cached one.

Audit log

Creating and revoking keys on the dashboard is recorded in your workspace's audit log as dashboard.api_key.created and dashboard.api_key.revoked. Read it with GET /v1/audit_logs, using a key with the audit:read scope; see Audit logs. Revoking a JWT isn't recorded there.

When to rotate at once

  • The key appeared in a Git push, a CI log, a screenshot or a ticket.
  • A laptop or server that held the key was lost or compromised.
  • Someone who had access to the key left.
Token rotation · poolse docs