Every WebSocket connection authenticates with one of two credentials. API keys are long-lived and belong on your servers. Tokens are short-lived JWTs your backend mints for end users, scoped to exactly the channels and operations each user needs.
API keys
A key is created in the dashboard per app and looks like myapp.kid_a1b2c3:sk_secretpart. The part before the colon is the public key id, the rest is the secret.
Use a key directly wherever the credential stays private: server processes, workers, scripts.
const realtime = new Realtime({
key: process.env.FOONY_API_KEY!,
clientId: 'worker-1',
});
Each key carries its own capability, editable in the dashboard. A key whose capability only grants public demo channels can even ship in a browser, which is how the homepage demo works, but the normal browser path is tokens.
Client tokens
A token is an HS256 JWT signed with your key. It carries the user’s clientId, a capability, and an expiry: 1 hour by default, 24 hours at most. Browsers get tokens from your backend, so your key never leaves the server.
On the client, pass an authCallback that fetches a fresh token. The SDK calls it on connect and again whenever the token expires or a reconnect needs one:
const realtime = new Realtime({
authCallback: async () => {
const response = await fetch('/api/realtime/token');
return response.text();
},
});
See Server auth for the matching backend handler.
Capabilities
A capability is a JSON object mapping channel patterns to allowed operations:
{
"chat:*": ["subscribe", "publish", "presence", "history"],
"announcements": ["subscribe"]
}
The four operations:
| Operation | Allows |
|---|---|
subscribe |
Receiving messages, watching presence, catching up via fetch |
publish |
Publishing messages |
presence |
Entering, updating, and leaving presence |
history |
Reading stored messages |
Patterns match three ways: an exact name (announcements), a prefix (chat:* matches chat:lobby and chat:rooms:42), or everything (*).
Intersection on minting
When your backend mints a token, the requested capability is intersected with the key’s capability rather than rejected when it asks for too much. Overlapping patterns narrow to the tighter one, operations narrow to the common set, and patterns with nothing in common are dropped. Only a token with no remaining grants at all is refused.
This makes broad request templates safe: asking for {"*": ["*"]} from a key that only holds chat:* subscribe just yields that.
Errors
| Code | Meaning | What to do |
|---|---|---|
40101 |
Missing or invalid credentials | Check the key or token you sent |
40102 |
Token expired | The SDK refreshes via authCallback automatically. With a static token, mint a new one |
40301 |
Capability does not permit the operation on this channel | Widen the token’s capability, or the key’s in the dashboard |