Client integration

Xshathra Realtime (Centrifugo behind /realtime) carries WebSocket traffic for your apps. The console gives each project a server_api_key / client_api_key pair for your product (HTTP publish proxy, app identity). Use server_api_key on trusted backends to call Xshathra token and publish endpoints. Deployment engine secrets stay internal.

1. Create a project and copy your keys

Sign in at /login, open the dashboard, and create a project (e.g. production vs staging). Each project has one key pair: server_api_key and client_api_key. Keep server_api_key on servers you control only; never ship it in a browser or mobile binary.

2. WebSocket URL (same origin as the app)

Use TLS in production. The path is fixed:

wss://YOUR_HOST/realtime/connection/websocket

Local dev (Next on 3001): ws://localhost:3001/realtime/connection/websocket. Set NEXT_PUBLIC_XSHATHRA_REALTIME_URL in the app env to the full ws: or wss: URL for browser clients.

Server publish from your backend (project key): POST https://YOUR_HOST/api/realtime/publish with Authorization: apikey YOUR_SERVER_API_KEY. For private channel joins, mint subscription JWTs with POST /api/realtime/subscription-token — see API.

3. Get tokens from Xshathra

After authenticating your app user, your backend requests short-lived realtime tokens from Xshathra using the project server_api_key.

POST https://YOUR_HOST/api/realtime/token
Authorization: apikey YOUR_SERVER_API_KEY
Content-Type: application/json

{
  "userId": "user_123",
  "ttlSeconds": 900,
  "channels": ["public:lobby"]
}
POST https://YOUR_HOST/api/realtime/subscription-token
Authorization: apikey YOUR_SERVER_API_KEY
Content-Type: application/json

{
  "userId": "user_123",
  "channel": "chat:proj_123:room_abc",
  "ttlSeconds": 900
}

Response:

{
  "token": "eyJhbGciOi...",
  "expiresInSeconds": 900
}

Pass that token to the client over HTTPS. Centrifugo expects the connection JWT in the first protocol connect frame (what centrifuge-js sends when you set token in the client options), not as a raw ?token= query on the WebSocket URL — that pattern often fails the upgrade with HTTP 400. WebSocket URL:

wss://YOUR_HOST/realtime/connection/websocket

Centrifuge-compatible clients (e.g. centrifuge-js) accept a token in the constructor — see the client connection token section in Centrifugo docs.

CLI smoke test:

The engine negotiates the WebSocket subprotocol centrifuge-json (see Centrifuge's handler_websocket.go). Tools must send Sec-WebSocket-Protocol: centrifuge-json. If you see HTTP 400 on connect, your edge proxy (often Apache) may be forwarding /realtime as plain HTTP instead of tunneling the upgrade — enable WebSocket proxying (e.g. mod_proxy_wstunnel, ws:// rewrite + [P,L], preserve Upgrade / Connection).

TOKEN=$(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_123"}' | jq -r .token)

# If curl cannot reach the host: HTTPS_PROXY=http://127.0.0.1:10808 curl ...  or  ALL_PROXY=socks5h://127.0.0.1:10808 curl ...

npx wscat \
  -H "Sec-WebSocket-Protocol: centrifuge-json" \
  -c "wss://YOUR_HOST/realtime/connection/websocket?cf_ws_frame_ping_pong=true"
# After Connected, paste (use the TOKEN variable value as the JSON string):
{"id":1,"connect":{"token":"PASTE_JWT_HERE"}}

Xshathra signs and validates engine-facing JWT details server-side. Your integration only needs project keys and these first-party endpoints.

4. Patterns for games and apps

  • Match / room channel — one channel per match; server publishes state ticks; clients subscribe after matchmaking assigns them.
  • Lobby or presence — lightweight channels for party invites, ready checks, or “who is online” using realtime presence where enabled.
  • Global announcements — broadcast channels for maintenance or version banners; validate publish only on your backend.
  • Non-game apps — same model: collaboration sessions, live cursors, or dashboards; keep authoritative writes on the server.

Related