Realtime
Overview
The realtime system exposes a lightweight HTTP/WebSocket:
- A WebSocket endpoint for authenticated clients:
wss://event.centia.io/ - Accepts a JWT token and (for sub‑users) a list of relations they need events from.
- Broadcasts batched event notifications to all connected clients that match relations they listen to.
- A simple health endpoint:
/ping→pong.
Authentication and connection
The WebSocket endpoint is:
- wss://event.centia.io?token=...&rels=...
Query parameters:
token(required): A Centia.io JWT.rels(optional): Comma‑separated list of relations the client wants events from (e.g.,rockhall.inductees,rockhall.stars).
Connection flow:
- Server validates the token in a worker.
- If sub-user, server enforces per‑relation access for each entry in
rels. Unauthorized relations result in an error message and socket close. - On success, the client is registered.
Errors are returned as JSON and the socket is closed. Examples:
- Missing token:
{ "type": "error", "error": "missing_token", "message": "Missing token" } - Invalid token:
{ "type": "error", "error": "invalid_token", "message": "..." } - Unauthorized rels:
{ "type": "error", "error": "not_allowed", "message": "Not allowed to access this resource: <rel>" }
Receiving database change events
To enable database change events, set emit_events to true in the table schema:
PATCH https://api.centia.io/api/v4/schemas/my_schema/tables/my_table/events
Content-Type: application/json
Authorization: Bearer <token>
{
"emit_events": true
}
Events are batched and delivered to all connected clients subscribed to the table.
Batching defaults:
- 10 notifications or
- 2 seconds (whichever comes first),
- checked every 1 second.
When a batch is flushed, a message is sent to the clients:
{
"type": "batch",
"db": "my_database",
"batch": {
"my_database": {
"my_schema.my_table": {
"INSERT": [["key","value"]],
"UPDATE": [["key","value"]],
"DELETE": [["key","value"]],
"full_data": [ { "col1": "val", "col2": 123 } ]
},
"another_schema.table": {}
}
}
}
Subscriptions
Instead of receiving all events for the database, you can dynamically subscribe to specific tables with server‑side filtering. Send a JSON message over an open WebSocket connection:
{
"type": "subscription",
"id": "sub1",
"schema": "my_schema",
"rel": "my_table"
}
The server acknowledges with:
{
"type": "subscription_ack",
"id": "sub1"
}
Once subscribed, you only receive batches that match your subscription — the server filters before sending.
A single client can register multiple subscriptions on different tables by sending additional subscription messages.
Shapes — filtering and column selection
Subscriptions support shapes: server‑side row filtering and column projection so the client only receives the data it needs.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Must be "subscription" |
id | string | yes | A client‑chosen identifier for this subscription |
schema | string | yes | Database schema name |
rel | string | yes | Table name |
where | string | no | SQL‑like filter applied to full_data rows before delivery |
columns | string | no | Comma‑separated list of columns to include in full_data (empty = all columns) |
op | string | no | Limit to a single operation: "INSERT", "UPDATE", or "DELETE" (empty = all operations) |
Where syntax
The where clause supports a subset of SQL:
- Comparison operators:
=,!=,<>,>,<,>=,<= - Logical operators:
AND,OR, parentheses for grouping IN/NOT INwith value listsLIKE/ILIKEwith%and_wildcards
Example with shape
{
"type": "subscription",
"id": "active-users",
"schema": "public",
"rel": "users",
"where": "status = 'active' AND age >= 18",
"columns": "id,name,email",
"op": "UPDATE"
}
This subscribes to UPDATE events on public.users, only receiving rows where status = 'active' AND age >= 18, projected to the columns id, name, and email.
GraphQL subscriptions
For GraphQL‑based subscriptions with typed queries and field selection, see GraphQL Subscriptions.
Client examples
Broadcast (legacy)
JavaScript (browser):
<script>
const token = "<your-jwt>";
const rels = "public.parcels,public.addresses";
const ws = new WebSocket(`wss://event.centia.io/?token=${encodeURIComponent(token)}&rels=${encodeURIComponent(rels)}`);
ws.onopen = () => {
console.log("connected");
ws.send("SELECT NOW() AS ts");
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'batch') {
console.log("DB batch:", msg.batch);
} else {
console.log("SQL result:", msg);
}
};
ws.onclose = () => console.log("closed");
ws.onerror = (err) => console.error("ws error", err);
</script>
Subscription with shape
const token = "<your-jwt>";
const ws = new WebSocket(`wss://event.centia.io/?token=${encodeURIComponent(token)}`);
ws.onopen = () => {
// Subscribe to updates on public.users with filtering
ws.send(JSON.stringify({
type: "subscription",
id: "active-users",
schema: "public",
rel: "users",
where: "status = 'active'",
columns: "id,name,email",
op: "UPDATE"
}));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'subscription_ack') {
console.log(`Subscribed: ${msg.id}`);
} else if (msg.type === 'batch') {
console.log("Filtered batch:", msg.batch);
}
};
Node (ws)
import WebSocket from 'ws';
const token = process.env.CENTIA_TOKEN;
const ws = new WebSocket(`wss://event.centia.io/?token=${encodeURIComponent(token)}`);
ws.on('open', () => ws.send('SELECT 42 AS answer'));
ws.on('message', (data) => console.log(JSON.parse(data.toString())));