Your backend has two jobs: mint short-lived tokens so browsers never see your API key, and optionally publish or read data over REST.
Mint a token locally
createJwt signs a token in-process with no network call, which makes token endpoints fast and dependency-free:
import { createJwt } from '@foony/realtime';
export async function GET(request: Request) {
const userId = await requireUser(request);
const token = await createJwt(
{
clientId: userId,
capability: { [`chat:${userId}:*`]: ['subscribe', 'publish', 'presence', 'history'] },
ttlMs: 60 * 60 * 1000,
},
{ key: process.env.FOONY_API_KEY! },
);
return new Response(token);
}
The browser side points an authCallback at this endpoint, see Auth and capabilities. On a Go backend, the Go SDK mints the same tokens with realtime.CreateJWT.
Mint a token via the service
If you prefer the service to sign, request one over REST. The response includes the expiry so you can cache until then:
import { Rest } from '@foony/realtime';
const rest = new Rest({ key: process.env.FOONY_API_KEY! });
const details = await rest.auth.requestToken({
clientId: 'user-123',
capability: { 'chat:*': ['subscribe', 'publish'] },
ttl: 60 * 60 * 1000,
});
console.log(details.token, details.expires);
Without the SDK, the same operation is POST /keys/{keyName}/requestToken with Basic auth, documented in the REST API.
Publish from the server
The Rest client publishes, reads history, and snapshots presence over plain HTTPS, which suits serverless functions and cron jobs that should not hold a socket open:
const channel = rest.channels.get('announcements');
await channel.publish('deploy', { version: '2026.07.02' });
For a long-lived server process that also needs to receive messages, use the regular Realtime client with key auth instead.
Rules to remember
- Token lifetime defaults to 1 hour and is capped at 24 hours.
- The minted capability is the intersection of what you request and what the key holds.
clientIdis required when minting: it is the identity presence and messages attribute to the user.