Skip to content

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.

typescript
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.

typescript
interface AuthMessage {
  type: 'auth'
  token: string
}
FieldTypeRequiredDescription
typestringYes"auth"
tokenstringYesBearer token without "Bearer " prefix

Example:

json
{
  "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.

typescript
interface PongMessage {
  type: 'pong'
}

Example:

json
{ "type": "pong" }

subscribeEvents

Subscribe to one or more event streams. Requires an authenticated connection.

typescript
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
}
FieldTypeRequiredDescription
typestringYes"subscribeEvents"
requestIdstringNoCorrelation ID for response
subscriptionsEventSubscription[]YesSubscriptions to add

Subscription fields:

FieldTypeRequiredDescription
idstringYesClient-assigned unique ID for this subscription
pathstringYesResource path (see Event Streaming)
eventsstring[]YesEvent types to receive

Example:

json
{
  "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.

typescript
interface UnsubscribeEventsMessage {
  type: 'unsubscribeEvents'
  requestId?: string
  ids: string[]
}
FieldTypeRequiredDescription
typestringYes"unsubscribeEvents"
requestIdstringNoCorrelation ID for response
idsstring[]YesSubscription IDs to remove

Example:

json
{
  "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.

typescript
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")
}
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")

Example:

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 (Experimental)

Destroy a live query and release its server-side resources.

typescript
interface DestroyLiveQueryMessage {
  type: 'destroyLiveQuery'
  id: string
}
FieldTypeRequiredDescription
typestringYes"destroyLiveQuery"
idstringYesLive query ID to destroy

Example:

json
{
  "type": "destroyLiveQuery",
  "id": "lq-active-orders"
}

Response: liveQueryDestroyed or error


Server → Client Messages

authenticated

Successful authentication acknowledgment.

typescript
interface AuthenticatedMessage {
  type: 'authenticated'
}

Example:

json
{ "type": "authenticated" }

ping

Server-initiated keep-alive. Respond with pong within 30 seconds.

typescript
interface PingMessage {
  type: 'ping'
  timestamp: string  // ISO 8601
}

Example:

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

error

An error response to a client request, or a fatal connection error.

typescript
interface ErrorMessage {
  type: 'error'
  code: string
  message: string
  requestId?: string  // Echoed from the request that caused the error
  details?: unknown
}
FieldTypeDescription
typestring"error"
codestringMachine-readable error code
messagestringHuman-readable description
requestIdstringCorrelation ID from the request, if any
detailsunknownAdditional context

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

Example:

json
{
  "type": "error",
  "code": "INVALID_PATH",
  "message": "Unknown resource path: widgets",
  "requestId": "req-5"
}

warning

A non-fatal notification, typically indicating backpressure.

typescript
interface WarningMessage {
  type: 'warning'
  code: string
  message: string
  subscriptionId?: string
}
FieldTypeDescription
typestring"warning"
codestringWarning code
messagestringHuman-readable description
subscriptionIdstringAffected subscription ID, if applicable

Warning codes:

CodeDescription
QUEUE_OVERFLOWEvents were dropped due to slow client consumption

Example:

json
{
  "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.

typescript
interface SubscribedEventsMessage {
  type: 'subscribedEvents'
  requestId?: string
  subscriptions: SubscriptionConfirmation[]
}

interface SubscriptionConfirmation {
  id: string
  path: string
  events: string[]
}

Example:

json
{
  "type": "subscribedEvents",
  "requestId": "req-1",
  "subscriptions": [
    { "id": "all-jobs", "path": "jobs", "events": ["jobScheduled", "jobStarted", "jobCompleted", "jobFailed"] }
  ]
}

unsubscribedEvents

Confirmation that event subscriptions have been removed.

typescript
interface UnsubscribedEventsMessage {
  type: 'unsubscribedEvents'
  requestId?: string
  ids: string[]
}

Example:

json
{
  "type": "unsubscribedEvents",
  "ids": ["all-jobs"]
}

event

An event notification for registered subscriptions.

typescript
interface EventMessage {
  type: 'event'
  subscriptionIds: string[]
  eventType: string
  path: string
  data: unknown
  timestamp: string  // ISO 8601
}
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

Job 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
}
actorCheckpoint
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"
}

liveQueryCreated (Experimental)

Acknowledgment that a live query was created. Followed immediately by one or more liveQueryData messages.

typescript
interface LiveQueryCreatedMessage {
  type: 'liveQueryCreated'
  id: string
  totalCount: number | null
  capped: boolean
}
FieldTypeDescription
typestring"liveQueryCreated"
idstringLive query ID (echoed from createLiveQuery)
totalCountnumber | nullTotal number of matching nodes, or null if unknown
cappedbooleantrue if result set was truncated at the record cap

Example:

json
{
  "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.

typescript
interface LiveQueryDataMessage {
  type: 'liveQueryData'
  id: string
  data: unknown[]
  hasMore: boolean
  capped: boolean
}
FieldTypeDescription
typestring"liveQueryData"
idstringLive query ID
dataunknown[]Batch of graph nodes
hasMorebooleanfalse on the final batch
cappedbooleantrue if the result set was truncated at the record cap

Example:

json
{
  "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).

typescript
interface LiveQueryAddedNodeMessage {
  type: 'liveQueryAddedNode'
  id: string
  nodeId: string
  data: unknown
}

Example:

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 (Experimental)

A node in the live query's result set has changed.

typescript
interface LiveQueryUpdatedNodeMessage {
  type: 'liveQueryUpdatedNode'
  id: string
  nodeId: string
  data: unknown
}

Example:

json
{
  "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).

typescript
interface LiveQueryRemovedNodeMessage {
  type: 'liveQueryRemovedNode'
  id: string
  nodeId: string
}

Example:

json
{
  "type": "liveQueryRemovedNode",
  "id": "lq-active-orders",
  "nodeId": "550e8400-e29b-41d4-a716-446655440000"
}

liveQueryDestroyed (Experimental)

Confirmation that a live query has been destroyed.

typescript
interface LiveQueryDestroyedMessage {
  type: 'liveQueryDestroyed'
  id: string
}

Example:

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

TypeScript Types

typescript
// 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
}

© 2024 Hantera AB. All rights reserved.