WebSocket
Hantera exposes a unified WebSocket endpoint for real-time communication. It supports two capabilities:
- Event Streaming — push notifications when things happen in your system (jobs, actor state changes)
- Live Queries (experimental) — maintain a reactive, server-side query result that automatically updates as data changes
WARNING
Preview API: The WebSocket API is currently in preview and subject to change before final release.
Endpoint
wss://{hostname}/wsReplace {hostname} with your tenant hostname (e.g. tenant.core.ams.hantera.cloud).
Connection Lifecycle
Open WebSocket connection
typescriptconst ws = new WebSocket('wss://{hostname}/ws')Authenticate
The first message must be an
authmessage containing your access token:json{ "type": "auth", "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." }Send the token without the
Bearerprefix. Authentication must complete within 10 seconds or the server closes the connection.Receive confirmation
On success:
json{ "type": "authenticated" }Subscribe to events or create live queries
Once authenticated you can send
subscribeEventsandcreateLiveQuerymessages (see below).Respond to keep-alive pings
The server sends
pingevery 30 seconds. Respond withpongwithin 30 seconds:json{ "type": "pong" }
Event Streaming
Subscribe to server-side events using the subscribeEvents message.
Subscribe
{
"type": "subscribeEvents",
"requestId": "req-1",
"subscriptions": [
{
"id": "my-jobs",
"path": "jobs",
"events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"]
}
]
}The server confirms with subscribedEvents:
{
"type": "subscribedEvents",
"requestId": "req-1",
"subscriptions": [
{ "id": "my-jobs", "path": "jobs", "events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"] }
]
}Receiving Events
Events arrive as event messages:
{
"type": "event",
"subscriptionIds": ["my-jobs"],
"eventType": "jobCompleted",
"path": "jobs",
"data": {
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"jobDefinitionId": "sync-inventory",
"finishedAt": "2025-12-07T21:45:05.000Z",
"elapsedMs": 4000.5
},
"timestamp": "2025-12-07T21:45:05.000Z"
}Unsubscribe
{
"type": "unsubscribeEvents",
"ids": ["my-jobs"]
}Supported Paths and Events
Jobs
| Path | Description |
|---|---|
jobs | All job lifecycle events |
jobs/{jobId} | Events for a specific job |
| Event | Description |
|---|---|
jobScheduled | Job created in pending state |
jobStarted | Job execution began |
jobCompleted | Job finished successfully |
jobFailed | Job execution failed |
Job Statistics
| Path | Description |
|---|---|
job-definitions | Statistics for all job definitions |
job-definitions/{jobDefinitionId} | Statistics for a specific job type |
| Event | Description |
|---|---|
jobStatistics | Live bucket update with aggregated counters |
Actors
| Path | Description |
|---|---|
actors | All actor checkpoint events |
actors/{actorType} | Checkpoints for a specific actor type |
actors/{actorType}/{actorId} | Checkpoints for a specific actor instance |
Actor types: orders, payments, skus, and custom actors.
| Event | Description |
|---|---|
actorCheckpoint | Checkpoint created in actor |
INFO
The checkpoint event does not include mutation details. Query the actor's state via the Graph API to see what changed.
Backpressure
Event streaming uses best-effort delivery. When your client consumes events slower than they are produced, the server queues events and drops old ones when the queue fills. A warning message with code QUEUE_OVERFLOW is sent to notify you:
{
"type": "warning",
"code": "QUEUE_OVERFLOW",
"message": "5 events dropped for subscription 'my-jobs' due to slow consumption",
"subscriptionId": "my-jobs"
}When you receive an overflow warning, re-sync your state from the Graph API to recover any missed changes.
WARNING
Dropped events are permanently lost. For workflows requiring guaranteed delivery, use Rules with webhooks instead.
Live Queries (Experimental)
WARNING
Experimental: Live Queries are an experimental feature. Message shapes and behaviour may change.
A live query runs a Graph API query server-side and sends you the initial results plus incremental updates as data changes.
Create a Live Query
Send a createLiveQuery message with your graph query nested in a query field:
{
"type": "createLiveQuery",
"id": "lq-orders",
"query": {
"edge": "orders",
"filter": "status == 'processing'",
"orderBy": "createdAt desc"
}
}The server responds with liveQueryCreated:
{
"type": "liveQueryCreated",
"id": "lq-orders",
"totalCount": 42,
"capped": false
}capped is true when the result set exceeds the maximum record limit (default 1 000). The query will only track the capped set.
Receiving Initial Data
After creation the server streams the initial result set as one or more liveQueryData messages:
{
"type": "liveQueryData",
"id": "lq-orders",
"data": [ { "id": "...", "status": "processing", ... }, ... ],
"hasMore": true,
"capped": false
}{
"type": "liveQueryData",
"id": "lq-orders",
"data": [ ... ],
"hasMore": false,
"capped": false
}When hasMore is false, the initial load is complete.
Incremental Updates
As underlying data changes, the server sends targeted update messages:
{ "type": "liveQueryAddedNode", "id": "lq-orders", "nodeId": "660e8400-...", "data": { ... } }
{ "type": "liveQueryUpdatedNode", "id": "lq-orders", "nodeId": "550e8400-...", "data": { ... } }
{ "type": "liveQueryRemovedNode", "id": "lq-orders", "nodeId": "550e8400-..." }Destroy a Live Query
When you no longer need a live query, send destroyLiveQuery to release server-side resources:
{
"type": "destroyLiveQuery",
"id": "lq-orders"
}The server responds with liveQueryDestroyed:
{
"type": "liveQueryDestroyed",
"id": "lq-orders"
}Error Handling
Errors are returned as error messages:
{
"type": "error",
"code": "INVALID_PATH",
"message": "Unknown resource path: invalid/path",
"requestId": "req-5"
}See the WebSocket API Reference for the full list of error codes.
Complete Example
const ws = new WebSocket('wss://core.your-tenant.hantera.cloud/ws')
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'auth', token: 'your-access-token' }))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.type) {
case 'authenticated':
// Subscribe to job events
ws.send(JSON.stringify({
type: 'subscribeEvents',
subscriptions: [{
id: 'jobs',
path: 'jobs',
events: ['jobScheduled', 'jobStarted', 'jobCompleted', 'jobFailed']
}]
}))
break
case 'event':
console.log(`[${msg.eventType}]`, msg.data)
break
case 'ping':
ws.send(JSON.stringify({ type: 'pong' }))
break
case 'error':
console.error(msg.code, msg.message)
break
}
}Best Practices
- Reconnect on disconnect — implement automatic reconnection with exponential backoff. Re-authenticate and re-subscribe after reconnecting.
- Subscribe selectively — only subscribe to paths and events you need to reduce message volume.
- Handle overflow warnings — when you receive
QUEUE_OVERFLOW, re-sync from the Graph API to recover missed changes. - Destroy live queries — call
destroyLiveQuerywhen a query is no longer needed to free server memory. - Use request correlation — include
requestIdin messages to correlate responses.