Skip to main content

WebSocket Subscription Protocols

Hugr supports two WebSocket protocols for subscriptions: graphql-ws (JSON-based) and IPC (Arrow binary). Both support multiple concurrent subscriptions per connection.

ProtocolEndpointFormatUse Case
graphql-ws/subscribeJSONBrowser clients, standard GraphQL tooling
IPC/ipcArrow binaryHigh-performance clients (Go, Python), large datasets

graphql-ws Protocol​

The /subscribe endpoint implements the graphql-ws protocol — the standard WebSocket sub-protocol for GraphQL.

Connection Lifecycle​

Client Server
|--- connection_init ----------->|
|<-- connection_ack -------------|
|--- subscribe {id, query} ----->|
|<-- next {id, payload} --------| (repeated)
|<-- complete {id} -------------|

1. Initialize Connection​

{"type": "connection_init", "payload": {"X-Hugr-Api-Key": "sk-..."}}

The server responds with:

{"type": "connection_ack"}

Authentication headers can be passed in the payload of connection_init.

2. Subscribe​

{
"id": "1",
"type": "subscribe",
"payload": {
"query": "subscription { query(interval: 5) { devices { id status } } }"
}
}

3. Receive Data​

The server sends next messages with row data:

{
"id": "1",
"type": "next",
"payload": {
"data": {
"devices": [
{"id": 1, "status": "active"},
{"id": 2, "status": "idle"}
]
}
}
}

4. Completion​

When the subscription ends (count exhausted or one-shot):

{"id": "1", "type": "complete"}

The client can also send complete to cancel a subscription.

Multiple Subscriptions​

Each subscription has a unique id. Multiple subscriptions can run concurrently on the same connection:

{"id": "sub-devices", "type": "subscribe", "payload": {"query": "subscription { query(interval: 5) { devices { id } } }"}}
{"id": "sub-alerts", "type": "subscribe", "payload": {"query": "subscription { query(interval: 10) { alerts { id } } }"}}

JavaScript Example​

Using the graphql-ws npm package:

import { createClient } from "graphql-ws";

const client = createClient({
url: "ws://localhost:15000/subscribe",
connectionParams: {
"X-Hugr-Api-Key": "sk-...",
},
});

// One-shot subscription
const unsubscribe = client.subscribe(
{
query: `subscription {
query {
devices { id name status }
}
}`,
},
{
next(value) {
console.log("Data:", value.data);
},
error(err) {
console.error("Error:", err);
},
complete() {
console.log("Done");
},
}
);

// Cancel
// unsubscribe();

IPC Protocol (Arrow Binary)​

The /ipc endpoint uses a custom binary protocol optimized for high-throughput data delivery. Results are encoded as Apache Arrow IPC streams, avoiding JSON serialization overhead.

Connection Lifecycle​

Client Server
|--- subscribe {id, query} -------->|
|<-- [binary: Arrow schema] --------|
|<-- [binary: Arrow record batch] --| (repeated)
|<-- [text: part_complete] ---------|
|<-- [text: subscription_complete] -|

Subscribe​

Send a JSON text frame:

{
"type": "subscribe",
"subscription_id": "sub-1",
"payload": {
"query": "subscription { query(interval: 5) { devices { id status } } }"
}
}

Binary Frames​

The server sends Arrow IPC data as binary WebSocket frames. Each binary frame contains either an Arrow schema or a record batch.

Arrow schema metadata carries routing information:

Metadata KeyDescription
subscription_idMatches the subscription that produced this data
pathData path (e.g., data.devices)

The client uses these metadata fields to route binary frames to the correct reader.

Reader per Path​

For each unique (subscription_id, path) pair, the client constructs a single Arrow record reader. This reader spans all ticks of the subscription — new record batches are appended as they arrive. The reader closes when a subscription_complete text frame is received for that subscription.

Text Frames​

Control messages are sent as JSON text frames:

Part complete — signals the end of one tick for a specific path:

{
"type": "part_complete",
"subscription_id": "sub-1",
"path": "data.devices"
}

Subscription complete — signals the subscription has ended:

{
"type": "subscription_complete",
"subscription_id": "sub-1"
}

Unsubscribe​

Send a text frame to cancel a subscription:

{
"type": "unsubscribe",
"subscription_id": "sub-1"
}

Multiple Subscriptions​

Multiple subscriptions can run concurrently on a single IPC connection. Each subscription is identified by its subscription_id, and binary frames are routed using the schema metadata.

{"type": "subscribe", "subscription_id": "sub-devices", "payload": {"query": "subscription { query(interval: 5) { devices { id } } }"}}
{"type": "subscribe", "subscription_id": "sub-alerts", "payload": {"query": "subscription { query(interval: 10) { alerts { id } } }"}}

Protocol Comparison​

Featuregraphql-wsIPC
Data formatJSONArrow binary
Serialization overheadHigher (JSON encode/decode)Minimal (zero-copy capable)
Browser supportNativeRequires Arrow JS
Multiple subscriptionsBy idBy subscription_id
Completion signalcomplete messagesubscription_complete text frame
Best forWeb apps, debugging, small payloadsAnalytics, large datasets, Go/Python clients

See Also​