WebSocket API

Realtime subscriptions over WebSocket.

Endpoint: WS /api/realtime (same host and port as HTTP)

Connection

ws://localhost:8080/api/realtime
ws://localhost:8080/api/realtime?token=<access_token>

Authentication is optional. Pass a JWT access token via:

  • Authorization: Bearer <token> header on the upgrade request
  • ?token=<token> query parameter (recommended for browsers)

On connect, the server sends:

{
  "type": "connected",
  "clientId": "rt_550e8400-e29b-41d4-a716-446655440000"
}

Client Messages

All messages are JSON text frames.

Subscribe

{
  "action": "subscribe",
  "channel": "posts.created"
}

Response:

{
  "type": "subscribed",
  "channel": "posts.created"
}

Unsubscribe

{
  "action": "unsubscribe",
  "channel": "posts.created"
}

Response:

{
  "type": "unsubscribed",
  "channel": "posts.created"
}

Ping

{
  "action": "ping"
}

Response:

{
  "type": "pong"
}

Server Messages

Event

When a subscribed event is emitted on the Event Bus:

{
  "type": "event",
  "event": {
    "id": "evt_123",
    "type": "posts.created",
    "timestamp": "2026-06-08T12:00:00.000Z",
    "source": "collections",
    "payload": {
      "id": "rec_1",
      "title": "Hello",
      "createdAt": "2026-06-08T12:00:00.000Z",
      "updatedAt": "2026-06-08T12:00:00.000Z"
    }
  }
}

Error

{
  "type": "error",
  "code": "invalid_channel",
  "message": "Channel name is required"
}
CodeDescription
invalid_jsonMessage is not valid JSON
invalid_actionUnknown action
invalid_channelChannel name is empty or malformed
subscription_limitMore than 50 subscriptions per connection

Channels

Collection Events

{collection}.created
{collection}.updated
{collection}.deleted

Example: posts.created, posts.updated, posts.deleted

Collection events are filtered by the collection read permission. Clients only receive events for records they are allowed to read via REST.

Wildcards

{collection}.*

Example: posts.* matches posts.created, posts.updated, and posts.deleted.

Auth Events

auth.login
auth.logout
auth.register

Storage Events

storage.uploaded
storage.deleted

Function Events

function.started
function.completed
function.failed

Job Events

job.started
job.completed
job.failed

System Events

system.collection.created

Example

const ws = new WebSocket("ws://localhost:8080/api/realtime?token=YOUR_TOKEN");

ws.onopen = () => {
  ws.send(JSON.stringify({ action: "subscribe", channel: "posts.*" }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  if (message.type === "event") {
    console.log(message.event.type, message.event.payload);
  }
};

Then create a post:

curl -X POST http://localhost:8080/api/posts \
  -H 'Content-Type: application/json' \
  -d '{"title":"Hello"}'

The WebSocket client receives a posts.created event.