WebSocket¶
The WebSocket channel pushes live KPI snapshots to connected dashboards, eliminating polling.
Connection¶
Authenticates the same way as REST. The endpoint tries, in order:
| # | Method | Example |
|---|---|---|
| 1 | Authorization: Bearer <token> header |
Authorization: Bearer eyJhbGci… |
| 2 | Sub-protocol jwt.<token> |
Sec-WebSocket-Protocol: jwt.eyJhbGci… |
| 3 | ACCESS_TOKEN cookie (browser) |
auto-sent |
| 4 | ?token= query param |
CLI/tests only |
The token must decode to a valid JWT with type == "access".
Rejections (connection closed with 1008 Policy Violation):
- Missing/invalid token
- Rate limit exceeded (
ws_limiterper client IP)
Initial Snapshot¶
On connect, the server immediately sends the current KPI state:
payload is the same shape as GET /api/kpis.
Message Protocol¶
| Direction | Message | Meaning |
|---|---|---|
| Server → Client | kpi:snapshot |
Full KPI state |
| Client → Server | ping |
Liveness check |
| Server → Client | pong |
Reply to ping |
| Server → Client | ping |
Server-side keepalive (every 30 s idle) |
sequenceDiagram
participant C as Client (React)
participant W as WS Server /ws/kpis
participant R as Redis Pub/Sub
C->>W: connect (Bearer jwt)
W-->>C: kpi:snapshot (initial)
W->>R: subscribe events:new
R-->>W: new events published
W-->>C: kpi:snapshot (recomputed)
loop Keepalive
C->>W: ping
W-->>C: pong
end
C-->>W: disconnect
Keepalive¶
The client sends ping text frames. The server also sends ping if the socket is idle for 30 s, and closes the connection when the client is unreachable.
Manager & Limits¶
- Connection manager tracks all active sockets by user ID (
manager.connect/disconnect). - Reconnect logic lives in the frontend
useKpisSockethook; it auto-reconnects with backoff. - The browser's HttpOnly cookie path works automatically for WS because cookies are sent with the handshake.
Related¶
- KPIs API — the
kpi:snapshotpayload shape - Frontend / Real-time — client consumption