Skip to main content
Authenticated API

WebSocket connections require a valid JWT Bearer token. Connect via the API gateway at /v1/ws.

WebSocket Subscriptions

Subscribe to real-time event channels.

Overview

Subscriptions allow you to receive specific events:

FeatureDescription
ChannelsTopic-based event streams
FiltersNarrow events by criteria
PresenceTrack connected users
MultiplexingMultiple subscriptions per connection

Subscribe

Request

{
"type": "subscribe",
"id": "req-001",
"channel": "orders",
"params": {
"location_id": "loc-xyz789"
}
}

Response

{
"type": "response",
"id": "req-001",
"success": true,
"subscription_id": "sub-abc123",
"channel": "orders",
"params": {
"location_id": "loc-xyz789"
}
}

Unsubscribe

Request

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

Response

{
"type": "response",
"success": true,
"subscription_id": "sub-abc123",
"message": "Unsubscribed successfully"
}

Available Channels

orders

Order lifecycle events.

{
"channel": "orders",
"params": {
"location_id": "loc-xyz789",
"types": ["dine_in", "takeout"],
"statuses": ["open", "preparing"]
}
}

Events: order.created, order.updated, order.status_changed, order.paid, order.closed

kds

Kitchen display system events.

{
"channel": "kds",
"params": {
"location_id": "loc-xyz789",
"station": "grill"
}
}

Events: kds.ticket.created, kds.ticket.bumped, kds.ticket.late, kds.ticket.recalled

inventory

Inventory and stock events.

{
"channel": "inventory",
"params": {
"location_id": "loc-xyz789",
"alert_types": ["low_stock", "out_of_stock"]
}
}

Events: inventory.low_stock, inventory.out_of_stock, inventory.received

tables

Table and seating events.

{
"channel": "tables",
"params": {
"location_id": "loc-xyz789"
}
}

Events: table.seated, table.cleared, table.reserved

analytics

Real-time analytics updates.

{
"channel": "analytics",
"params": {
"location_id": "loc-xyz789",
"metrics": ["revenue", "orders", "avg_ticket"]
}
}

Events: analytics.metric_update, analytics.hourly_summary

delivery

Delivery platform events.

{
"channel": "delivery",
"params": {
"location_id": "loc-xyz789",
"platforms": ["doordash", "ubereats"]
}
}

Events: delivery.order_received, delivery.driver_assigned, delivery.picked_up

notifications

User notifications and alerts.

{
"channel": "notifications",
"params": {
"user_id": "user-abc123"
}
}

Events: notification.alert, notification.message

voice

Voice AI session events.

{
"channel": "voice",
"params": {
"location_id": "loc-xyz789",
"lane": 1
}
}

Events: voice.session_started, voice.order_completed


Filter Parameters

Location Filter

Most channels support location filtering:

{
"channel": "orders",
"params": {
"location_id": "loc-xyz789"
}
}

Multi-Location

Subscribe to multiple locations:

{
"channel": "orders",
"params": {
"location_ids": ["loc-xyz789", "loc-abc456"]
}
}

Event Type Filter

Subscribe to specific events only:

{
"channel": "orders",
"params": {
"location_id": "loc-xyz789",
"events": ["order.created", "order.paid"]
}
}

Status Filter

Filter by entity status:

{
"channel": "orders",
"params": {
"location_id": "loc-xyz789",
"statuses": ["open", "preparing"]
}
}

Presence

Track who's connected to a channel.

Join Presence

{
"type": "presence_join",
"channel": "orders",
"params": {
"location_id": "loc-xyz789"
},
"user_info": {
"name": "Sarah Server",
"role": "server"
}
}

Presence Events

{
"type": "presence",
"event": "user_joined",
"channel": "orders",
"data": {
"user_id": "user-sarah",
"user_info": {
"name": "Sarah Server",
"role": "server"
},
"joined_at": "2026-01-18T14:30:00Z"
}
}

Get Presence List

{
"type": "presence_list",
"channel": "orders",
"params": {
"location_id": "loc-xyz789"
}
}

Response

{
"type": "presence_list_response",
"channel": "orders",
"users": [
{
"user_id": "user-sarah",
"user_info": {"name": "Sarah Server", "role": "server"},
"joined_at": "2026-01-18T14:30:00Z"
},
{
"user_id": "user-john",
"user_info": {"name": "John Manager", "role": "manager"},
"joined_at": "2026-01-18T14:25:00Z"
}
]
}

Subscription Management

List Active Subscriptions

{
"type": "list_subscriptions"
}

Response

{
"type": "subscriptions_list",
"subscriptions": [
{
"id": "sub-abc123",
"channel": "orders",
"params": {"location_id": "loc-xyz789"},
"created_at": "2026-01-18T14:30:00Z"
},
{
"id": "sub-def456",
"channel": "kds",
"params": {"location_id": "loc-xyz789", "station": "grill"},
"created_at": "2026-01-18T14:30:05Z"
}
]
}

Unsubscribe All

{
"type": "unsubscribe_all"
}

Subscription Errors

Channel Not Found

{
"type": "error",
"id": "req-001",
"code": "channel_not_found",
"message": "Channel 'invalid' does not exist"
}

Unauthorized

{
"type": "error",
"id": "req-001",
"code": "unauthorized",
"message": "Not authorized to subscribe to this channel",
"channel": "orders",
"required_permission": "orders.read"
}

Invalid Parameters

{
"type": "error",
"id": "req-001",
"code": "invalid_params",
"message": "Missing required parameter: location_id",
"channel": "orders"
}

Subscription Limit

{
"type": "error",
"id": "req-001",
"code": "subscription_limit",
"message": "Maximum subscriptions reached",
"current": 50,
"limit": 50
}

Best Practices

1. Use Specific Filters

Reduce unnecessary events:

// Good - specific filters
{
"channel": "orders",
"params": {
"location_id": "loc-xyz789",
"statuses": ["preparing"],
"events": ["order.status_changed"]
}
}

// Avoid - no filters
{
"channel": "orders",
"params": {}
}

2. Batch Subscriptions

Subscribe to multiple channels efficiently:

// Send all subscriptions together
const subscriptions = [
{ channel: 'orders', params: { location_id } },
{ channel: 'kds', params: { location_id, station: 'grill' } },
{ channel: 'tables', params: { location_id } }
];

for (const sub of subscriptions) {
ws.send(JSON.stringify({
type: 'subscribe',
id: `sub-${Date.now()}`,
...sub
}));
}

3. Handle Reconnection

Re-subscribe after reconnection:

const activeSubscriptions = new Map();

function subscribe(channel, params) {
const id = generateId();
ws.send(JSON.stringify({ type: 'subscribe', id, channel, params }));
activeSubscriptions.set(id, { channel, params });
return id;
}

function resubscribeAll() {
for (const [_, sub] of activeSubscriptions) {
subscribe(sub.channel, sub.params);
}
}

ws.onopen = () => {
if (reconnecting) {
resubscribeAll();
}
};

4. Clean Up Subscriptions

Unsubscribe when no longer needed:

// When leaving a view
function cleanup() {
for (const subId of viewSubscriptions) {
ws.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: subId
}));
}
viewSubscriptions.clear();
}