API reference
HTTP endpoints for Xshathra Realtime: minting connection and subscription JWTs, and publishing to channels from your backend. All paths are relative to your deployment origin (for example https://xshathra.example.com or http://localhost:3001). Dashboard sign-in and account flows live in the web app only — they are not listed here.
Authorization (project keys)
Token and publish endpoints require the project server_api_key from the console (dashboard → project). Never expose it in browsers or mobile clients.
Authorization: apikey YOUR_SERVER_API_KEY
Bearer YOUR_SERVER_API_KEY is also accepted for compatibility. Replace the placeholder with the full key string from the console.
Control plane (JSON)
/api/realtime/tokenIssues a short-lived connection JWT for the realtime engine. Your backend calls this after you know which end-user is connecting. The JWT is passed to the Centrifuge-compatible client (for example token in centrifuge-js), not as a WebSocket query string. Signing uses deployment secrets; the browser never sees the engine HMAC key.
Request headers
Content-Type: application/jsonAuthorization: apikey …(projectserver_api_key)
Request body (JSON)
userId(string, required) — stable id for this connection; becomes the JWTsubclaim.ttlSeconds(number, optional) — lifetime in seconds, default900; allowed range30–3600.channels(string array, optional) — connection token can include default channel names (must match namespaces configured on the engine, e.g.public:room_1).info(object, optional) — arbitrary JSON object stored in token claims (visible to the engine; keep it small).
Example — curl
curl -sS -X POST "https://YOUR_HOST/api/realtime/token" \
-H "Authorization: apikey YOUR_SERVER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "user_01HZEXAMPLE",
"ttlSeconds": 900,
"channels": ["public:lobby"],
"info": { "displayName": "Player One" }
}'Example — Node fetch
const response = await fetch(`${process.env.XSHATHRA_ORIGIN}/api/realtime/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `apikey ${process.env.XSHATHRA_SERVER_API_KEY}`
},
body: JSON.stringify({
userId: session.userId,
ttlSeconds: 900,
channels: ["public:lobby"]
})
});
const body = await response.json();
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
const { token, expiresInSeconds } = body;Success response (200)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresInSeconds": 900
}400— invalid JSON or body (seeerror).401— missing or invalidAuthorization, or unknownserver_api_key.503— app missingCENTRIFUGO_TOKEN_HMAC_SECRET_KEY(or legacy aliasCENTRIFUGO_TOKEN_HMAC_SECRET).
/api/realtime/subscription-tokenIssues a short-lived JWT that authorizes subscribing to a single channel (typical for private rooms). Use when your engine namespace requires subscription tokens for that channel. If you only use public namespaces and allow open subscribe, you may rely on the connection token only — see Client integration.
Request body (JSON)
userId(string, required)channel(string, required) — full channel name (e.g.public:room_abc)ttlSeconds(optional, same range as token endpoint)info(object, optional)
Example — curl
curl -sS -X POST "https://YOUR_HOST/api/realtime/subscription-token" \
-H "Authorization: apikey YOUR_SERVER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "user_01HZEXAMPLE",
"channel": "public:room_abc",
"ttlSeconds": 600
}'Success response (200)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"channel": "public:room_abc",
"expiresInSeconds": 600
}Pass token to the client subscription API (for example getToken in centrifuge-js).
/api/realtime/publishProxies a publish request to the realtime engine after validating your project server_api_key. The request body is forwarded unchanged to the engine's HTTP publish API (same JSON shape Centrifugo expects on POST /api/publish). Use this from trusted backends only so message content and channel names stay under your control.
Typical JSON body (Centrifugo publish)
{
"channel": "public:lobby",
"data": {
"type": "chat.message",
"text": "Hello",
"senderId": "user_01HZEXAMPLE"
}
}Example — curl
curl -sS -X POST "https://YOUR_HOST/api/realtime/publish" \
-H "Authorization: apikey YOUR_SERVER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "public:lobby",
"data": { "type": "ping", "at": "2026-03-30T12:00:00.000Z" }
}'401— missing or invalid project key.503— app missingCENTRIFUGO_UPSTREAM_URLand/orCENTRIFUGO_API_KEY.- Response status and body match the engine response when the proxy forwards successfully.
Same-origin realtime surface
Browser clients should use the same host as your app for WebSocket and engine HTTP paths under /realtime. Your reverse proxy must allow WebSocket upgrades on /realtime/ (see Client integration).
/realtime/healthEngine liveness. Use for monitoring or to confirm the proxy reaches the data plane.
Example
curl -sS -i "https://YOUR_HOST/realtime/health"
/realtime/connection/websocketCentrifuge protocol endpoint. Connect with a Centrifuge-compatible client using a connection JWT from POST /api/realtime/token. Production URL shape:
wss://YOUR_HOST/realtime/connection/websocket
Optional local test page: /socket-auth-test.html (step-by-step token + connect + subscribe).
Projects and API keys
server_api_key and client_api_key are created in the dashboard per project. Only server_api_key is used for the HTTP endpoints on this page. For channel naming and client SDK usage, see Client integration.
