Realtime
Broadcast channels over WebSockets: every message published to a channel reaches every connection subscribed to it.
WebSocket API, or use the JavaScript SDK's
channel() helper, which wraps it.Connecting
Open a WebSocket to your project with a channel name
([a-zA-Z0-9:_-], up to 128 chars) and any project JWT —
the anon key, an end-user access token, or the service-role key:
const ws = new WebSocket(
"wss://<ref>.kethosbase.com/realtime/v1/ws" +
"?channel=room:42&token=" + anonKey
);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// { event: "broadcast", channel: "room:42", payload: { … } }
};
Publishing
Send a JSON frame on an open socket:
ws.send(JSON.stringify({
event: "broadcast",
channel: "room:42",
payload: { kind: "chat", text: "hello" }
}));
Or publish over HTTP — handy from a backend:
curl -X POST "https://<ref>.kethosbase.com/realtime/v1/broadcast" \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"channel": "room:42", "payload": {"kind": "ping"}}'
→ { "delivered": 5 }
Connection lifecycle and re-auth
The token is presented in the query string and validated at
connect time: an invalid or expired token is rejected during the
WebSocket handshake, and the channel prefix (db: vs a plain name)
fixes what the connection may do. There is no in-band re-auth frame — the
connection carries the identity it opened with.
- Reconnecting. If a socket drops, open a new one to the
same channel to resume. A plain broadcast channel simply continues; the
SDK's
channel()reconnects and re-subscribes for you. - Refreshed tokens. Because the token is checked only at connect, present a newly refreshed access token by reconnecting with it. An access token that expires mid-connection does not tear the socket down, but the next connection needs a valid one.
- No replay. A
db:change feed is live-only — anything that happened while you were disconnected is not replayed (see below), so treat a reconnect as "resume from now", not "catch up".
Limits
| Limit | Value |
|---|---|
| Message size | 64 KiB |
| Connections per IP | 20 |
| Connections per project | 50 (Starter) / 500 (Pro) / 2,000 (Business) |
| HTTP publishes per IP | 600 / minute |
A connection that publishes too fast receives
{"event": "rate_limited"} frames; over-limit connection
attempts are rejected with 429.
Database change feeds
Channels named db:{table} are change feeds:
instead of carrying what clients publish, they stream the table's
inserts, updates, and deletes straight from the database.
const ws = new WebSocket(
"wss://<ref>.kethosbase.com/realtime/v1/ws" +
"?channel=db:todos&token=" + serviceRoleKey
);
ws.onmessage = (e) => {
const { event, payload } = JSON.parse(e.data);
// event: "change"
// payload: { action: "insert", table: "todos",
// record: { id: 1, task: "ship it", done: false }, old: null }
};
- Subscribing requires the service_role key — change feeds bypass Row-Level Security, so they are for your backend, which fans out to its own users.
- The feed is live-only: changes made while nobody is subscribed are not replayed. Treat it as a notification stream, not an event log.
- Only tables with a primary key (or an explicit
REPLICA IDENTITY) are streamed; a table joins the feed the next time adb:subscriber connects after its creation. oldcarries the previous primary-key columns by default; setREPLICA IDENTITY FULLon a table to receive entire old rows.- Nobody can publish into
db:channels — the database is the only publisher. Rows above the 64 KiB frame limit arrive as a{"event":"too_large"}notice instead.