Subscribe to WebSocket events
ScaiWave streams real-time events over a single WebSocket per client. The protocol is JSON-over-WS; subscribe to a tenant scope and the server fans out everything you have permission to see.
The endpoint#
1 | |
The token is passed via query string because browser WebSockets don't support custom headers. The server validates it the same way HTTP middleware does and rejects bad tokens with a close frame.
Don't log the URL. It contains a Bearer token. Most server log configs scrub it, but be careful in browser devtools and proxy logs.
Hello#
After the upgrade succeeds, the server sends:
1 2 3 4 5 6 7 | |
If you don't see this within a couple of seconds, the token wasn't
accepted; check the close frame's code and reason.
What you receive#
The same event types whether you sent the action or someone else
did. Each event carries a type discriminator.
Room events#
swp.room.message— a message was sent.swp.room.reaction— a reaction was added.swp.room.redaction— an event was redacted.swp.room.message.chunk— streaming chunk (during AI generation). Includescontent.chunk_textandcontent.done.swp.room.member— membership changed (join/leave/ban).swp.room.ai_engagement— AI engagement state changed.swp.room.name— room renamed.swp.room.topic— topic changed.swp.room.read_marker— your own read marker advanced (cross-device sync).
AI events#
swp.ai.status— AI's status update ("thinking", "calling web_search", "generating", null when done).swp.ai.context_usage— current token usage on the AI's prompt.swp.plan.updated— full plan snapshot (state + steps).
Sidekick events#
swp.sidekick.spawned— sidekick created in this room.swp.sidekick.result— sidekick completed; result attached.swp.sidekick.cancel— sidekick cancelled.
Presence / typing#
typing—{room_id, participant_id, typing: bool}.presence—{participant_id, status: "online"|"idle"|...}.
Calls#
swp.call.invite— incoming call (with timeout).swp.call.state— call participant state changed.
Federation#
swp.federation.event— opaque to the client; surfaces newly- federated room state changes.
Bridges#
swp.bridge.message— message arrived from a bridge.swp.bridge.status— bridge state change.
Filtering#
The server fans out everything you can see (every room you're a
member of). To narrow what you process, filter client-side on
data.room_id or data.type.
Reconnect#
The server idles connections after 60s without activity from either side (ping/pong is automatic). On a clean close (code 1000) or abrupt disconnect, reconnect with exponential backoff.
ScaiWave doesn't replay missed events on reconnect — use the sync endpoint:
1 | |
Long-polls; returns everything you missed since <last-stream-position>,
then either resolves (data available) or after timeout (idle —
re-poll). The web client uses this on reconnect, then resumes WS.
Minimal client (Python)#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Minimal client (TypeScript)#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Where to go next#
- Reference: WebSocket events — full catalog.
- Tutorial: Write a plugin — emit your own event types.