Troubleshooting

Common problems, what causes them, and the fix. Every error code is also listed on Errors.

Tokens and auth

Minting a token returns 403 forbidden

The message says Missing required scope: users:manage. Your API key was created without the users:manage scope. Scopes can't be added to an existing key.

Fix: create a new key with users:manage ticked (the dashboard ticks it by default), deploy it, and revoke the old one. See API keys.

Minting a token returns 404

POST /v1/users/:user_id/tokens takes poolse's user id (the UUID in the id field of POST /v1/users), not your external_id. Anything that isn't a UUID, or a user that doesn't exist in this workspace, returns 404.

Fix: call POST /v1/users with the external_id first (it is an upsert, safe to repeat) and mint with the id it returns.

Minting returns 429 rate_limited

More than 60 mints per minute on one API key. Your token route is probably called far more often than needed, for example on every render.

Fix: let the SDK cache the token; it only calls getToken when the token is about to expire or a request returns 401. Wait for the Retry-After seconds before retrying.

The SDK throws AuthError

A request got 401. The SDK then asks your getToken for a new token and retries once; AuthError means the retry got 401 too. Look at the error's code:

codeCause
invalid_user_tokenThe token is expired or invalid, or its user no longer exists.
token_revokedThe token was revoked with POST /admin/v1/tokens/revoke.
user_blockedThe user is blocked (is_blocked: true).
missing_authorizationNo Authorization header was sent.
malformed_authorizationThe header isn't Bearer <token>, for example because getToken returned an empty value.

Fix: make your token route mint a fresh token on every call instead of returning a cached one. Check it:

const res = await fetch('/api/chat-token', { method: 'POST' });
const { token } = await res.json();
const { exp, sub } = JSON.parse(atob(token.split('.')[1]));
console.log('expires', new Date(exp * 1000), 'user', sub); // expect ~1 hour from now

If exp is in the past, your route returns a stale token. If the clock on your server is wrong, tokens can look expired; check it with date -u.

WebSocket

useRealtimeStatus() stays on connecting or reconnecting

The socket handshake is being refused, and the SDK keeps retrying. The causes are the same as for AuthError: an expired, revoked or empty token, or a blocked user. Fix the token route first.

Other causes:

  • Mixed content. The page is served over HTTPS but wsUrl is ws://…. Leave wsUrl unset: the SDK derives wss:// from an https:// apiUrl.
  • A network that blocks WebSockets. poolse has no long-polling fallback. REST keeps working, but nothing updates live.

Pass onSocketError in the SDK config to log socket errors.

Messages sent while the app was offline don't appear

Events sent while the socket was down are not replayed. From 3.1, useMessages and useConversations reload from REST when the socket reconnects. If you use @poolse/sdk directly, refetch when the status returns to connected. On React Native, mount useAppStateLifecycle() so the socket is paused and resumed cleanly. See Realtime.

400 bad_request or 404 for an id that exists

Every id is a UUID. A value that isn't one gets 404 when it is in the path, and 400 bad_request ("An id in the request is not a valid UUID.") when it is in the body or query string, for example quoted_message_id or attachment_ids. Check you are sending poolse's id, not your own.

Next.js: "two React copies" / useContext returns null

You added @poolse/* to transpilePackages in next.config.mjs. Remove it and restart next dev. See Integration.

- transpilePackages: ['@poolse/sdk', '@poolse/react', '@poolse/react-ui'],

Plan limits

402 mau_cap_exceeded

The workspace has its plan's number of monthly active users this month (Free 1,000, Pro 10,000, Scale 50,000), and this user has not been active yet this month. Users already active keep working; the count resets on the 1st (UTC). details has count and cap.

Fix: request a bigger plan on Plan & usage. See Limits & quotas.

402 attachment_too_large or 402 attachment_total_cap_exceeded

Your hosted storage limits are reached. On Free, one file can be at most 1 MB and all attachments together 300 MB; on Pro and Scale, 25 MB per file and 500 MB in total. Files over 100 MB are refused on every plan with 422 byte_size_too_large.

Fix: delete attachments you no longer need, request a bigger plan, or connect your own bucket on Storage. With your own bucket there is no storage limit from poolse and files can be up to 100 MB. See Bring your own storage.

Webhooks stopped arriving

Check, in this order:

  1. The webhook is Active on its dashboard page.
  2. Its events. A webhook with some events ticked only gets those; one with none ticked gets every event.
  3. The delivery quota. On Free, after 1,000 deliveries in 24 hours new events are not delivered and not queued, and no error is shown anywhere. Look at Webhook deliveries, last 24 hours on Plan & usage.
  4. The deliveries table on the webhook's page. Retrying or Failed rows show the status code your server returned; hover the code for the error. Redirects count as failures, and so does taking longer than 10 seconds to answer.

402 webhook_count_cap_exceeded

Free allows 5 webhooks. Delete one, or request Pro. A webhook with no events ticked receives every event, so you may need fewer webhooks than you think.

Webhook signatures don't match

  • You signed re-encoded JSON instead of the raw body.
  • You left t. out of the signed string.
  • You used another webhook's secret.

Code that works: Webhook verification.

Attachment upload fails

  • The upload URL expired. It is valid for 15 minutes. Request a new one.

Sending a message with an attachment returns 422 send_failed

One cause: the file you stored is bigger than the byte_size you declared when you requested the upload URL. The upload URL doesn't check the size, so poolse checks it when the attachment is sent. Request a new upload URL with the real size.

See Attachment uploads.

Sending fails with 403 forbidden for everyone

If every send in the workspace fails with 403 while reads work, check for a Workspace suspended banner in the dashboard. A suspended or canceled workspace can't send messages, request upload URLs or create webhooks. See Billing.

Phones don't ring or get notifications

  • Check the pills on the dashboard's Push page. Waiting for devices means no device has registered; Incomplete on iOS means the app registered its VoIP token but not its notification token.
  • Use Send a test there to see what Apple or Google answer for each device.
  • On iOS, the Bundle ID must match your app's bundle identifier exactly.
  • No chat notifications at all? Send chat notifications may be off; it is off for workspaces created before poolse sent them.

Full walkthrough: Push setup and Ringing phones end to end.

Dashboard

Locked out of two-step sign-in

Choose another method on the sign-in screen: a recovery code, or an email code if you turned those on. If none works, email [email protected].

I deleted my workspace and want it back

You can't. Deletion removes the workspace and its encryption key, and poolse can't restore it. You can sign up again with the same email, but you get a new, empty workspace. See Danger zone.

Still stuck?

Email [email protected] with:

  • Your Workspace ID (dashboard: Account → Workspace, or tenant_id from GET /v1/whoami).
  • The failing request: method, path, status and the error code.
  • What you expected and what happened.
Troubleshooting · poolse docs