Realtime WebSocket
Subscribe to database change events as they happen. INSERT / UPDATE / DELETE through /sql fans out to every connected WebSocket client. Pro+ plans only.
Connect
const ws = new WebSocket( `wss://api.mosaic.site/v1/project/${slug}/realtime?apikey=${API_KEY}` );
API key goes in the query param — browsers can't send custom headers on WebSocket handshake.
Subscribe to changes
ws.onopen = () => {
// All changes on "tasks"
ws.send(JSON.stringify({ type: "subscribe", table: "tasks", event: "*" }));
// INSERTs only
ws.send(JSON.stringify({ type: "subscribe", table: "messages", event: "INSERT" }));
// With row filter
ws.send(JSON.stringify({
type: "subscribe", table: "tasks", event: "*",
filter: "user_id=eq.abc-123"
}));
};
Receive events
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "change") {
// { type, table, event: "INSERT"|"UPDATE"|"DELETE",
// new: {...}, old: null, row_count, timestamp }
console.log(msg.event, msg.table, msg.new);
}
if (msg.type === "ping") {
ws.send(JSON.stringify({ type: "pong" }));
}
};
Use RETURNING *To get row data in change events, the SQL that mutated the row needs to use
RETURNING *. Without it, the event still fires but new will be null.Filter syntax
column=eq.value— equality matchcolumn=in.(a,b,c)— inclusion match- No filter — every change on that table
Plan limits
| Plan | Connections | Subscriptions |
|---|---|---|
| Free / Starter | — | Disabled |
| Pro | 10 | 20 |
| Enterprise | 100 | 100 |
Message types
| Message | Direction | Description |
|---|---|---|
| subscribe | → Server | {type, table, event?, filter?} |
| unsubscribe | → Server | {type, table} |
| pong | → Server | Heartbeat response |
| system | ← Server | Connection status |
| subscribed | ← Server | Subscription confirmed |
| change | ← Server | Database change event |
| ping | ← Server | Heartbeat (every 30s) |
| error | ← Server | Error message |