Skip to main content

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: /pingpong.

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:

  1. Server validates the token in a worker.
  2. If sub-user, server enforces per‑relation access for each entry in rels. Unauthorized relations result in an error message and socket close.
  3. 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.

FieldTypeRequiredDescription
typestringyesMust be "subscription"
idstringyesA client‑chosen identifier for this subscription
schemastringyesDatabase schema name
relstringyesTable name
wherestringnoSQL‑like filter applied to full_data rows before delivery
columnsstringnoComma‑separated list of columns to include in full_data (empty = all columns)
opstringnoLimit 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 IN with value lists
  • LIKE / ILIKE with % 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):

index.html
<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

subscription.js
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)

index.js
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())));