Skip to content

WebSocket API Reference

This page documents every message type in the Hantera WebSocket protocol, grouped by capability. For connection setup, authentication and reconnection guidance, see WebSocket.

Endpoint: wss://{hostname}/ws

All messages are JSON text frames. Binary frames are not supported.

WARNING

Preview API: The WebSocket API is currently in preview and subject to change before final release.

Contents

GroupMessages
Connectionauth, authenticated, ping, pong
Errors and Warningserror, warning
Event SubscriptionsubscribeEvents, unsubscribeEvents, subscribedEvents, unsubscribedEvents, event
Live QueriescreateLiveQuery, destroyLiveQuery, liveQueryCreated, liveQueryData, liveQueryAddedNode, liveQueryUpdatedNode, liveQueryRemovedNode, liveQueryBatch, liveQueryDestroyed

Base Structure

Every message has a type field identifying its kind. Request messages may include a requestId, which the server echoes back on the corresponding response or error.

FieldTypeRequiredDescription
typestringYesMessage type
requestIdstringNoCorrelation ID, echoed on the response

Connection

Messages that establish and maintain the connection itself.

auth

Client → Server. Authenticates the connection. Must be the first message sent.

FieldTypeRequiredDescription
typestringYes"auth"
tokenstringYesBearer token without the "Bearer " prefix
json
{
  "type": "auth",
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response: authenticated, or error with AUTH_FAILED.

Authentication must complete within 10 seconds or the connection closes with code 4001.


authenticated

Server → Client. Confirms successful authentication. The connection is now ready for event subscriptions and live queries.

FieldTypeDescription
typestring"authenticated"
json
{ "type": "authenticated" }

ping

Server → Client. Keep-alive probe, sent every 30 seconds.

FieldTypeDescription
typestring"ping"
timestampstringISO 8601 timestamp
json
{
  "type": "ping",
  "timestamp": "2025-12-07T21:30:00.000Z"
}

pong

Client → Server. Response to a ping. Must be sent within 30 seconds or the connection closes with code 4002.

FieldTypeRequiredDescription
typestringYes"pong"
json
{ "type": "pong" }

Errors and Warnings

error

Server → Client. An error response to a request, or a fatal connection error.

FieldTypeDescription
typestring"error"
codestringMachine-readable error code
messagestringHuman-readable description
requestIdstringCorrelation ID from the request, if any
detailsunknownAdditional context
json
{
  "type": "error",
  "code": "INVALID_PATH",
  "message": "Unknown resource path: widgets",
  "requestId": "req-5"
}

Error codes:

CodeDescriptionConnection closed?
AUTH_REQUIREDFirst message was not authYes
AUTH_FAILEDInvalid or expired tokenYes
MESSAGE_TOO_LARGEMessage exceeds the size limitNo
INVALID_MESSAGEMalformed JSON or missing required fieldNo
UNKNOWN_MESSAGE_TYPEUnrecognised message typeNo
INVALID_PATHSubscription path not recognisedNo
INVALID_SCOPEInvalid event types for the given pathNo
SUBSCRIPTION_NOT_FOUNDunsubscribeEvents referenced an unknown IDNo
TOO_MANY_SUBSCRIPTIONSPer-connection event subscription limit reachedNo
TOO_MANY_LIVE_QUERIESPer-connection live query limit reachedNo
LIVE_QUERY_NOT_FOUNDdestroyLiveQuery referenced an unknown IDNo
FORBIDDENInsufficient permissions for the requested dataNo
INTERNAL_ERRORUnexpected server errorNo

warning

Server → Client. A non-fatal notification, typically indicating backpressure.

FieldTypeDescription
typestring"warning"
codestringWarning code
messagestringHuman-readable description
subscriptionIdstringAffected subscription ID, if applicable

Warning codes:

CodeDescription
QUEUE_OVERFLOWMessages were dropped due to slow client consumption
json
{
  "type": "warning",
  "code": "QUEUE_OVERFLOW",
  "message": "5 events dropped for subscription 'all-jobs' due to slow consumption",
  "subscriptionId": "all-jobs"
}

WARNING

Dropped events are permanently lost. Re-sync from the Graph API to recover missed changes. For workflows requiring guaranteed delivery, use Rules with webhooks instead.


Event Subscription

Push notifications for things that happen in your system. A subscription pairs a path (which resources) with a list of event types (which changes).

Delivery is best-effort: when a client consumes slower than events are produced, the server drops the oldest and emits a QUEUE_OVERFLOW warning.

Paths and Events

Jobs

PathDescription
jobsAll job lifecycle events
jobs/{jobId}Events for a specific job
EventDescription
jobScheduledJob created in pending state
jobStartedJob execution began
jobCompletedJob finished successfully
jobFailedJob execution failed

Job Definitions

PathDescription
job-definitionsStatistics for all job definitions
job-definitions/{jobDefinitionId}Statistics for a specific job type
EventDescription
jobStatisticsLive bucket update with aggregated counters

Job Queues

PathDescription
job-queuesAll job queue statistics
job-queues/{queue}Statistics for a specific queue
EventDescription
jobQueueStatisticsLive bucket update with queue depth and throughput

Actors

PathDescription
actorsAll 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.

EventDescription
checkpointCheckpoint 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.

Ingresses

PathDescription
ingressesStatistics for all ingresses
ingresses/{ingressId}Statistics for a specific ingress
EventDescription
ingressStatisticsLive bucket update with request counts and latency

Egresses

PathDescription
egressesStatistics for all egresses
egresses/{egressKey}Statistics for a specific egress
EventDescription
egressStatisticsLive bucket update with call counts and latency

Egress Hosts

PathDescription
egress-hostsStatistics for all egress hosts
egress-hosts/{transport}Statistics for hosts of one transport type
egress-hosts/{transport}/{host}Statistics for a specific host
EventDescription
egressHostStatisticsLive bucket update with call counts and latency by host

subscribeEvents

Client → Server. Subscribes to one or more event streams. Requires an authenticated connection.

FieldTypeRequiredDescription
typestringYes"subscribeEvents"
requestIdstringNoCorrelation ID for response
subscriptionsEventSubscription[]YesSubscriptions to add

Subscription fields:

FieldTypeRequiredDescription
idstringYesClient-assigned unique ID for this subscription
pathstringYesResource path (see Paths and Events)
eventsstring[]YesEvent types to receive
json
{
  "type": "subscribeEvents",
  "requestId": "req-1",
  "subscriptions": [
    {
      "id": "all-jobs",
      "path": "jobs",
      "events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"]
    }
  ]
}

Response: subscribedEvents or error.


unsubscribeEvents

Client → Server. Removes one or more event subscriptions.

FieldTypeRequiredDescription
typestringYes"unsubscribeEvents"
requestIdstringNoCorrelation ID for response
idsstring[]YesSubscription IDs to remove
json
{
  "type": "unsubscribeEvents",
  "ids": ["all-jobs"]
}

Response: unsubscribedEvents or error.


subscribedEvents

Server → Client. Confirms that subscriptions have been registered.

FieldTypeDescription
typestring"subscribedEvents"
requestIdstringEchoed from the request
subscriptionsSubscriptionConfirmation[]The registered subscriptions
json
{
  "type": "subscribedEvents",
  "requestId": "req-1",
  "subscriptions": [
    { "id": "all-jobs", "path": "jobs", "events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"] }
  ]
}

unsubscribedEvents

Server → Client. Confirms that subscriptions have been removed.

FieldTypeDescription
typestring"unsubscribedEvents"
requestIdstringEchoed from the request
idsstring[]The removed IDs
json
{
  "type": "unsubscribedEvents",
  "ids": ["all-jobs"]
}

event

Server → Client. An event notification for one or more registered subscriptions.

FieldTypeDescription
typestring"event"
subscriptionIdsstring[]IDs of the subscriptions that matched
eventTypestringSpecific event type (e.g. jobStarted)
pathstringFull path of the affected resource
dataunknownEvent payload (varies by event type)
timestampstringISO 8601 timestamp of the event
json
{
  "type": "event",
  "subscriptionIds": ["all-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"
}

Event data shapes:

jobScheduled
json
{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "jobDefinitionId": "sync-inventory",
  "scheduledAt": "2025-12-07T21:45:00.000Z",
  "parameters": { "source": "api" }
}
jobStarted
json
{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "jobDefinitionId": "sync-inventory",
  "startedAt": "2025-12-07T21:45:01.000Z"
}
jobCompleted
json
{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "jobDefinitionId": "sync-inventory",
  "finishedAt": "2025-12-07T21:45:05.000Z",
  "elapsedMs": 4000.5,
  "result": { "itemsSynced": 150 }
}
jobFailed
json
{
  "jobId": "660e8400-e29b-41d4-a716-446655440001",
  "jobDefinitionId": "sync-inventory",
  "finishedAt": "2025-12-07T21:46:00.000Z",
  "elapsedMs": 1500.0,
  "error": "Connection timeout"
}
jobStatistics
json
{
  "jobDefinitionId": "sync-inventory",
  "bucketTime": "2025-12-07T21:00:00.000Z",
  "scheduled": 45,
  "successful": 40,
  "failed": 2,
  "minExecution": 120.5,
  "maxExecution": 1250.0,
  "avgExecution": 450.3,
  "p95Execution": 1180.0
}
jobQueueStatistics
json
{
  "queue": "default",
  "bucketTime": "2025-12-07T21:00:00.000Z",
  "readyDepth": 12,
  "busyWorkers": 3,
  "workers": 5,
  "scheduled": 45,
  "completed": 40,
  "failed": 2
}
checkpoint
json
{
  "checkpointId": "770e8400-e29b-41d4-a716-446655440002",
  "actorType": "orders",
  "actorId": "550e8400-e29b-41d4-a716-446655440000",
  "identityId": "880e8400-e29b-41d4-a716-446655440003",
  "timestamp": "2025-12-07T21:47:00.000Z"
}
ingressStatistics
json
{
  "ingressId": "my-api",
  "transport": "http",
  "bucketTime": "2025-12-07T21:00:00.000Z",
  "requests": 850,
  "successes": 800,
  "clientErrors": 30,
  "serverErrors": 20,
  "minDurationMs": 5.2,
  "maxDurationMs": 340.1,
  "avgDurationMs": 28.7,
  "p95DurationMs": 95.3
}
egressStatistics
json
{
  "egressKey": "apps/psp.kustom/kustomApi",
  "bucketTime": "2025-12-07T21:00:00.000Z",
  "total": 200,
  "successful": 195,
  "failed": 5,
  "minDurationMs": 45.0,
  "maxDurationMs": 1200.5,
  "avgDurationMs": 210.3,
  "p95DurationMs": 580.0
}
egressHostStatistics
json
{
  "transport": "https",
  "host": "api.example.com",
  "bucketTime": "2025-12-07T21:00:00.000Z",
  "total": 200,
  "successful": 195,
  "failed": 5,
  "minDurationMs": 45.0,
  "maxDurationMs": 1200.5,
  "avgDurationMs": 210.3,
  "p95DurationMs": 580.0
}

Live Queries

WARNING

Experimental: Live queries are an experimental feature. Message shapes and behaviour may change.

A live query runs a Graph API query server-side, streams the initial result set, then pushes incremental updates as the underlying data changes.

The lifecycle is:

  1. createLiveQueryliveQueryCreated
  2. One or more liveQueryData messages carry the initial result set
  3. Incremental updates arrive as liveQueryAddedNode, liveQueryUpdatedNode, liveQueryRemovedNode or liveQueryBatch
  4. destroyLiveQueryliveQueryDestroyed

The Result Window

A live query tracks at most a fixed number of root nodes — the window — which holds the first N results in the query's sort order (default 1 000). When more rows match than the window can hold, capped is true.

The window is maintained as an ordered set. When a new node arrives:

  • If it sorts outside the window, it is ignored — no message is sent.
  • If it sorts inside the window, it is admitted and the current last node is displaced. Both changes arrive together in a single liveQueryBatch so the set never appears inconsistent.

To see results beyond the window, narrow the query's filter.


createLiveQuery

Client → Server. Creates a server-side reactive query.

FieldTypeRequiredDescription
typestringYes"createLiveQuery"
idstringYesClient-assigned unique ID for this live query
formatstringNoResponse format. Default: "default"
queryGraphQueryYesThe graph query to run and watch

query fields:

FieldTypeRequiredDescription
edgestringYesGraph edge to query (e.g. "orders")
filterstringNoFiltrera filter expression
orderBystringNoSort order in standard Graph format (e.g. "createdAt desc")

format values:

ValueDescription
"default"Node payloads are JSON objects
"table"Node payloads are arrays of column values, with a schema in the first liveQueryData message
json
{
  "type": "createLiveQuery",
  "id": "lq-active-orders",
  "query": {
    "edge": "orders",
    "filter": "status == 'processing'",
    "orderBy": "createdAt desc"
  }
}

Response: liveQueryCreated followed by liveQueryData messages, or error.


destroyLiveQuery

Client → Server. Destroys a live query and releases its server-side resources.

FieldTypeRequiredDescription
typestringYes"destroyLiveQuery"
idstringYesLive query ID to destroy
json
{
  "type": "destroyLiveQuery",
  "id": "lq-active-orders"
}

Response: liveQueryDestroyed or error with LIVE_QUERY_NOT_FOUND.


liveQueryCreated

Server → Client. Acknowledges creation. Followed immediately by one or more liveQueryData messages.

FieldTypeDescription
typestring"liveQueryCreated"
idstringLive query ID, echoed from the request
totalCountnumber | nullTotal matching nodes, or null if unknown
cappedbooleantrue if more rows match than the window can hold
json
{
  "type": "liveQueryCreated",
  "id": "lq-active-orders",
  "totalCount": 42,
  "capped": false
}

liveQueryData

Server → Client. A batch of nodes from the initial result set. When hasMore is false the initial load is complete and the query is tracking changes.

FieldTypeDescription
typestring"liveQueryData"
idstringLive query ID
dataunknown[]Batch of graph nodes
hasMorebooleanfalse on the final batch
cappedbooleantrue if more rows match than the window can hold
json
{
  "type": "liveQueryData",
  "id": "lq-active-orders",
  "data": [
    { "id": "550e8400-...", "status": "processing", "createdAt": "2025-12-07T10:00:00Z" }
  ],
  "hasMore": true,
  "capped": false
}

liveQueryAddedNode

Server → Client. A node has entered the result set — either newly created, or its data changed to match the filter.

FieldTypeDescription
typestring"liveQueryAddedNode"
idstringLive query ID
nodeIdstringID of the node that entered
dataunknownThe node payload
json
{
  "type": "liveQueryAddedNode",
  "id": "lq-active-orders",
  "nodeId": "660e8400-e29b-41d4-a716-446655440001",
  "data": { "id": "660e8400-...", "status": "processing", "createdAt": "2025-12-07T11:00:00Z" }
}

liveQueryUpdatedNode

Server → Client. A node already in the result set has changed. The payload is the node's complete new state, not a delta.

FieldTypeDescription
typestring"liveQueryUpdatedNode"
idstringLive query ID
nodeIdstringID of the changed node
dataunknownThe node payload
json
{
  "type": "liveQueryUpdatedNode",
  "id": "lq-active-orders",
  "nodeId": "550e8400-e29b-41d4-a716-446655440000",
  "data": { "id": "550e8400-...", "status": "processing", "total": 299.99 }
}

liveQueryRemovedNode

Server → Client. A node has left the result set — either deleted, its data no longer matches the filter, or it was displaced from a capped window.

FieldTypeDescription
typestring"liveQueryRemovedNode"
idstringLive query ID
nodeIdstringID of the removed node
json
{
  "type": "liveQueryRemovedNode",
  "id": "lq-active-orders",
  "nodeId": "550e8400-e29b-41d4-a716-446655440000"
}

liveQueryBatch

Server → Client. Several node changes that must be applied together as one atomic update.

The server coalesces changes into a batch whenever applying them individually would expose an inconsistent intermediate state. The most common case is a capped window swap, where an incoming node displaces the current last node — the removal and the addition arrive in the same message, so the result set never appears one item short.

Apply every entry in updates before re-rendering. Entries are ordered and should be applied in sequence.

FieldTypeDescription
typestring"liveQueryBatch"
idstringLive query ID
updatesBatchEntry[]Changes to apply together

Batch entry fields:

FieldTypeDescription
actionstring"added", "updated" or "removed"
nodeIdstringID of the affected node
dataunknownThe node payload. Omitted when action is "removed"

Each action has the same meaning as its standalone message: added matches liveQueryAddedNode, updated matches liveQueryUpdatedNode, and removed matches liveQueryRemovedNode.

Example — a capped window swap, where a newly created order sorts ahead of the window's last entry:

json
{
  "type": "liveQueryBatch",
  "id": "lq-active-orders",
  "updates": [
    {
      "action": "removed",
      "nodeId": "550e8400-e29b-41d4-a716-446655440000"
    },
    {
      "action": "added",
      "nodeId": "770e8400-e29b-41d4-a716-446655440002",
      "data": { "id": "770e8400-...", "status": "processing", "createdAt": "2025-12-07T12:00:00Z" }
    }
  ]
}

TIP

Clients that render a list should treat a batch as a single frame — apply all entries, then re-sort and re-render once. Applying entries one at a time and re-rendering between them reintroduces the flicker the batch exists to prevent.


liveQueryDestroyed

Server → Client. Confirms that a live query has been destroyed and its resources released.

FieldTypeDescription
typestring"liveQueryDestroyed"
idstringThe destroyed query ID
json
{
  "type": "liveQueryDestroyed",
  "id": "lq-active-orders"
}

Close Codes

CodeNameDescription
1000Normal ClosureClean disconnect by client
1001Going AwayServer shutting down
1009Message Too BigMessage exceeded size limit
4001Auth TimeoutNo auth message within 10s
4002Ping TimeoutNo pong received within 30s
4003Max ConnectionsConnection limit reached

© 2026 Hantera AB. Org. no.: 559242-9582