WebSocket API Reference
This page documents every message type in the Hantera WebSocket protocol. For a conceptual overview and usage guide, see WebSocket.
Endpoint: wss://{hostname}/ws
All messages are JSON text frames. Binary frames are not supported.
Base Structure
Every message has a type field identifying its kind.
interface BaseMessage {
type: string
requestId?: string // Optional correlation ID (requests only)
}Clients may include requestId in request messages. The server echoes it back in the response.
Client → Server Messages
auth
Authenticate the connection. Must be the first message sent after connecting.
interface AuthMessage {
type: 'auth'
token: string
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "auth" |
token | string | Yes | Bearer token without "Bearer " prefix |
Example:
{
"type": "auth",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response: authenticated or error (AUTH_FAILED)
pong
Response to a server ping. Must be sent within 30 seconds of receiving a ping.
interface PongMessage {
type: 'pong'
}Example:
{ "type": "pong" }subscribeEvents
Subscribe to one or more event streams. Requires an authenticated connection.
interface SubscribeEventsMessage {
type: 'subscribeEvents'
requestId?: string
subscriptions: EventSubscription[]
}
interface EventSubscription {
id: string // Client-assigned unique identifier
path: string // Resource path (e.g. "jobs", "actors/orders")
events: string[] // Event types to receive
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "subscribeEvents" |
requestId | string | No | Correlation ID for response |
subscriptions | EventSubscription[] | Yes | Subscriptions to add |
Subscription fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-assigned unique ID for this subscription |
path | string | Yes | Resource path (see Event Streaming) |
events | string[] | Yes | Event types to receive |
Example:
{
"type": "subscribeEvents",
"requestId": "req-1",
"subscriptions": [
{
"id": "all-jobs",
"path": "jobs",
"events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"]
}
]
}Response: subscribedEvents or error
unsubscribeEvents
Remove one or more event subscriptions.
interface UnsubscribeEventsMessage {
type: 'unsubscribeEvents'
requestId?: string
ids: string[]
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "unsubscribeEvents" |
requestId | string | No | Correlation ID for response |
ids | string[] | Yes | Subscription IDs to remove |
Example:
{
"type": "unsubscribeEvents",
"ids": ["all-jobs"]
}Response: unsubscribedEvents or error
createLiveQuery (Experimental)
Create a server-side reactive query. The server streams the initial result set and sends incremental updates as data changes.
interface CreateLiveQueryMessage {
type: 'createLiveQuery'
id: string // Client-assigned unique identifier
format?: string // Response format. Default: "default"
query: GraphQuery
}
interface GraphQuery {
edge: string // Graph edge name (e.g. "orders", "skus")
filter?: string // Filtrera filter expression
orderBy?: string // Order-by string in standard Graph format (e.g. "createdAt desc")
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "createLiveQuery" |
id | string | Yes | Client-assigned unique ID for this live query |
format | string | No | Response format. Default: "default" |
query | GraphQuery | Yes | The graph query to run and watch |
query fields:
| Field | Type | Required | Description |
|---|---|---|---|
edge | string | Yes | Graph edge to query (e.g. "orders") |
filter | string | No | Filtrera filter expression |
orderBy | string | No | Sort order in standard Graph format (e.g. "createdAt desc") |
Example:
{
"type": "createLiveQuery",
"id": "lq-active-orders",
"query": {
"edge": "orders",
"filter": "status == 'processing'",
"orderBy": "createdAt desc"
}
}Response: liveQueryCreated followed by liveQueryData messages, or error
destroyLiveQuery (Experimental)
Destroy a live query and release its server-side resources.
interface DestroyLiveQueryMessage {
type: 'destroyLiveQuery'
id: string
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "destroyLiveQuery" |
id | string | Yes | Live query ID to destroy |
Example:
{
"type": "destroyLiveQuery",
"id": "lq-active-orders"
}Response: liveQueryDestroyed or error
Server → Client Messages
authenticated
Successful authentication acknowledgment.
interface AuthenticatedMessage {
type: 'authenticated'
}Example:
{ "type": "authenticated" }ping
Server-initiated keep-alive. Respond with pong within 30 seconds.
interface PingMessage {
type: 'ping'
timestamp: string // ISO 8601
}Example:
{
"type": "ping",
"timestamp": "2025-12-07T21:30:00.000Z"
}error
An error response to a client request, or a fatal connection error.
interface ErrorMessage {
type: 'error'
code: string
message: string
requestId?: string // Echoed from the request that caused the error
details?: unknown
}| Field | Type | Description |
|---|---|---|
type | string | "error" |
code | string | Machine-readable error code |
message | string | Human-readable description |
requestId | string | Correlation ID from the request, if any |
details | unknown | Additional context |
Error codes:
| Code | Description | Connection closed? |
|---|---|---|
AUTH_REQUIRED | First message was not auth | Yes |
AUTH_FAILED | Invalid or expired token | Yes |
MESSAGE_TOO_LARGE | Message exceeds the size limit | No |
INVALID_MESSAGE | Malformed JSON or missing required field | No |
UNKNOWN_MESSAGE_TYPE | Unrecognised message type | No |
INVALID_PATH | Subscription path not recognised | No |
INVALID_SCOPE | Invalid event types for the given path | No |
SUBSCRIPTION_NOT_FOUND | unsubscribeEvents referenced an unknown ID | No |
TOO_MANY_SUBSCRIPTIONS | Per-connection event subscription limit reached | No |
TOO_MANY_LIVE_QUERIES | Per-connection live query limit reached | No |
LIVE_QUERY_NOT_FOUND | destroyLiveQuery referenced an unknown ID | No |
FORBIDDEN | Insufficient permissions for the requested data | No |
INTERNAL_ERROR | Unexpected server error | No |
Example:
{
"type": "error",
"code": "INVALID_PATH",
"message": "Unknown resource path: widgets",
"requestId": "req-5"
}warning
A non-fatal notification, typically indicating backpressure.
interface WarningMessage {
type: 'warning'
code: string
message: string
subscriptionId?: string
}| Field | Type | Description |
|---|---|---|
type | string | "warning" |
code | string | Warning code |
message | string | Human-readable description |
subscriptionId | string | Affected subscription ID, if applicable |
Warning codes:
| Code | Description |
|---|---|
QUEUE_OVERFLOW | Events were dropped due to slow client consumption |
Example:
{
"type": "warning",
"code": "QUEUE_OVERFLOW",
"message": "5 events dropped for subscription 'all-jobs' due to slow consumption",
"subscriptionId": "all-jobs"
}subscribedEvents
Confirmation that event subscriptions have been registered.
interface SubscribedEventsMessage {
type: 'subscribedEvents'
requestId?: string
subscriptions: SubscriptionConfirmation[]
}
interface SubscriptionConfirmation {
id: string
path: string
events: string[]
}Example:
{
"type": "subscribedEvents",
"requestId": "req-1",
"subscriptions": [
{ "id": "all-jobs", "path": "jobs", "events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"] }
]
}unsubscribedEvents
Confirmation that event subscriptions have been removed.
interface UnsubscribedEventsMessage {
type: 'unsubscribedEvents'
requestId?: string
ids: string[]
}Example:
{
"type": "unsubscribedEvents",
"ids": ["all-jobs"]
}event
An event notification for registered subscriptions.
interface EventMessage {
type: 'event'
subscriptionIds: string[]
eventType: string
path: string
data: unknown
timestamp: string // ISO 8601
}| Field | Type | Description |
|---|---|---|
type | string | "event" |
subscriptionIds | string[] | IDs of the subscriptions that matched |
eventType | string | Specific event type (e.g. jobStarted) |
path | string | Full path of the affected resource |
data | unknown | Event payload (varies by event type) |
timestamp | string | ISO 8601 timestamp of the event |
Job event data shapes:
jobScheduled
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"jobDefinitionId": "sync-inventory",
"scheduledAt": "2025-12-07T21:45:00.000Z",
"parameters": { "source": "api" }
}jobStarted
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"jobDefinitionId": "sync-inventory",
"startedAt": "2025-12-07T21:45:01.000Z"
}jobCompleted
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"jobDefinitionId": "sync-inventory",
"finishedAt": "2025-12-07T21:45:05.000Z",
"elapsedMs": 4000.5,
"result": { "itemsSynced": 150 }
}jobFailed
{
"jobId": "660e8400-e29b-41d4-a716-446655440001",
"jobDefinitionId": "sync-inventory",
"finishedAt": "2025-12-07T21:46:00.000Z",
"elapsedMs": 1500.0,
"error": "Connection timeout"
}jobStatistics
{
"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
}actorCheckpoint
{
"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"
}liveQueryCreated (Experimental)
Acknowledgment that a live query was created. Followed immediately by one or more liveQueryData messages.
interface LiveQueryCreatedMessage {
type: 'liveQueryCreated'
id: string
totalCount: number | null
capped: boolean
}| Field | Type | Description |
|---|---|---|
type | string | "liveQueryCreated" |
id | string | Live query ID (echoed from createLiveQuery) |
totalCount | number | null | Total number of matching nodes, or null if unknown |
capped | boolean | true if result set was truncated at the record cap |
Example:
{
"type": "liveQueryCreated",
"id": "lq-active-orders",
"totalCount": 42,
"capped": false
}liveQueryData (Experimental)
A batch of nodes from the initial result set. Sent in sequence after liveQueryCreated. When hasMore is false, the initial load is complete and the query is now tracking changes.
interface LiveQueryDataMessage {
type: 'liveQueryData'
id: string
data: unknown[]
hasMore: boolean
capped: boolean
}| Field | Type | Description |
|---|---|---|
type | string | "liveQueryData" |
id | string | Live query ID |
data | unknown[] | Batch of graph nodes |
hasMore | boolean | false on the final batch |
capped | boolean | true if the result set was truncated at the record cap |
Example:
{
"type": "liveQueryData",
"id": "lq-active-orders",
"data": [
{ "id": "550e8400-...", "status": "processing", "createdAt": "2025-12-07T10:00:00Z" }
],
"hasMore": true,
"capped": false
}liveQueryAddedNode (Experimental)
A node has entered the live query's result set (either newly created or its data changed to match the filter).
interface LiveQueryAddedNodeMessage {
type: 'liveQueryAddedNode'
id: string
nodeId: string
data: unknown
}Example:
{
"type": "liveQueryAddedNode",
"id": "lq-active-orders",
"nodeId": "660e8400-e29b-41d4-a716-446655440001",
"data": { "id": "660e8400-...", "status": "processing", "createdAt": "2025-12-07T11:00:00Z" }
}liveQueryUpdatedNode (Experimental)
A node in the live query's result set has changed.
interface LiveQueryUpdatedNodeMessage {
type: 'liveQueryUpdatedNode'
id: string
nodeId: string
data: unknown
}Example:
{
"type": "liveQueryUpdatedNode",
"id": "lq-active-orders",
"nodeId": "550e8400-e29b-41d4-a716-446655440000",
"data": { "id": "550e8400-...", "status": "processing", "total": 299.99 }
}liveQueryRemovedNode (Experimental)
A node has left the live query's result set (either deleted or its data no longer matches the filter).
interface LiveQueryRemovedNodeMessage {
type: 'liveQueryRemovedNode'
id: string
nodeId: string
}Example:
{
"type": "liveQueryRemovedNode",
"id": "lq-active-orders",
"nodeId": "550e8400-e29b-41d4-a716-446655440000"
}liveQueryDestroyed (Experimental)
Confirmation that a live query has been destroyed.
interface LiveQueryDestroyedMessage {
type: 'liveQueryDestroyed'
id: string
}Example:
{
"type": "liveQueryDestroyed",
"id": "lq-active-orders"
}Close Codes
| Code | Name | Description |
|---|---|---|
1000 | Normal Closure | Clean disconnect by client |
1001 | Going Away | Server shutting down |
1009 | Message Too Big | Message exceeded size limit |
4001 | Auth Timeout | No auth message within 10s |
4002 | Ping Timeout | No pong received within 30s |
4003 | Max Connections | Connection limit reached |
TypeScript Types
// Client → Server
type ClientMessage =
| AuthMessage
| PongMessage
| SubscribeEventsMessage
| UnsubscribeEventsMessage
| CreateLiveQueryMessage
| DestroyLiveQueryMessage
// Server → Client
type ServerMessage =
| AuthenticatedMessage
| PingMessage
| ErrorMessage
| WarningMessage
| SubscribedEventsMessage
| UnsubscribedEventsMessage
| EventMessage
| LiveQueryCreatedMessage
| LiveQueryDataMessage
| LiveQueryAddedNodeMessage
| LiveQueryUpdatedNodeMessage
| LiveQueryRemovedNodeMessage
| LiveQueryDestroyedMessage
interface GraphQuery {
edge: string
filter?: string
orderBy?: string
}