Skip to main content
Authenticated API

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

WebSocket Connection

Establish real-time connections to Olympus Cloud.

Overview

WebSocket connections provide real-time updates for:

FeatureDescription
Order UpdatesReal-time order status changes
Kitchen EventsKDS ticket updates
Inventory AlertsLow stock notifications
AnalyticsLive dashboard metrics
Voice AIStreaming audio sessions

Connection URL

wss://ws.olympuscloud.ai/v1

Environment URLs

EnvironmentURL
Productionwss://ws.olympuscloud.ai/v1
Stagingwss://ws.staging.olympuscloud.ai/v1
Developmentwss://ws.dev.olympuscloud.ai/v1

Authentication

Connection Authentication

Include the access token as a query parameter or header.

Query Parameter

const ws = new WebSocket('wss://ws.olympuscloud.ai/v1?token=YOUR_ACCESS_TOKEN');

First Message Authentication

const ws = new WebSocket('wss://ws.olympuscloud.ai/v1');

ws.onopen = () => {
ws.send(JSON.stringify({
type: 'auth',
token: 'YOUR_ACCESS_TOKEN'
}));
};

Authentication Response

{
"type": "auth_success",
"user_id": "user-abc123",
"tenant_id": "tenant-xyz789",
"permissions": ["orders.read", "orders.write"],
"expires_at": "2026-01-18T16:00:00Z"
}

Authentication Error

{
"type": "auth_error",
"code": "invalid_token",
"message": "Access token is expired or invalid"
}

Connection Lifecycle

Connect → Authenticate → Subscribe → Receive Events → Close

Heartbeat (every 30s)

Connection States

StateDescription
connectingEstablishing connection
authenticatingSending credentials
connectedReady to send/receive
reconnectingAttempting reconnection
disconnectedConnection closed

Heartbeat

The server sends ping messages every 30 seconds. Clients must respond with pong.

Server Ping

{
"type": "ping",
"timestamp": "2026-01-18T14:30:00Z"
}

Client Pong

{
"type": "pong",
"timestamp": "2026-01-18T14:30:00Z"
}

If no pong is received within 10 seconds, the server closes the connection.


Reconnection

Automatic Reconnection

Implement exponential backoff for reconnection:

class WebSocketClient {
constructor(url, token) {
this.url = url;
this.token = token;
this.reconnectAttempts = 0;
this.maxReconnectDelay = 30000;
this.connect();
}

connect() {
this.ws = new WebSocket(`${this.url}?token=${this.token}`);

this.ws.onopen = () => {
console.log('Connected');
this.reconnectAttempts = 0;
};

this.ws.onclose = (event) => {
if (!event.wasClean) {
this.reconnect();
}
};

this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}

reconnect() {
const delay = Math.min(
1000 * Math.pow(2, this.reconnectAttempts),
this.maxReconnectDelay
);

this.reconnectAttempts++;
console.log(`Reconnecting in ${delay}ms...`);

setTimeout(() => this.connect(), delay);
}
}

Resume Subscriptions

After reconnecting, resubscribe to all channels:

ws.onopen = () => {
// Re-authenticate
ws.send(JSON.stringify({ type: 'auth', token: accessToken }));

// Re-subscribe to previous channels
subscriptions.forEach(sub => {
ws.send(JSON.stringify({
type: 'subscribe',
channel: sub.channel,
params: sub.params
}));
});
};

Message Format

All messages use JSON format.

Outgoing Messages (Client to Server)

{
"type": "string",
"id": "optional-request-id",
...other fields
}

Incoming Messages (Server to Client)

{
"type": "string",
"channel": "optional-channel",
"data": {},
"timestamp": "2026-01-18T14:30:00Z"
}

Connection Limits

LimitValue
Max connections per user10
Max subscriptions per connection50
Max message size64KB
Connection timeout (idle)5 minutes

JavaScript Client Example

class OlympusWebSocket {
constructor(options) {
this.url = options.url || 'wss://ws.olympuscloud.ai/v1';
this.token = options.token;
this.onEvent = options.onEvent || (() => {});
this.subscriptions = new Map();
this.messageHandlers = new Map();
this.connected = false;
}

connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(`${this.url}?token=${this.token}`);

this.ws.onopen = () => {
this.connected = true;
this.startHeartbeat();
resolve();
};

this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};

this.ws.onclose = () => {
this.connected = false;
this.stopHeartbeat();
};

this.ws.onerror = reject;
});
}

handleMessage(message) {
switch (message.type) {
case 'ping':
this.ws.send(JSON.stringify({ type: 'pong' }));
break;
case 'event':
this.onEvent(message);
break;
case 'response':
const handler = this.messageHandlers.get(message.id);
if (handler) {
handler(message);
this.messageHandlers.delete(message.id);
}
break;
}
}

subscribe(channel, params = {}) {
const id = this.generateId();

return new Promise((resolve, reject) => {
this.messageHandlers.set(id, (response) => {
if (response.success) {
this.subscriptions.set(response.subscription_id, { channel, params });
resolve(response.subscription_id);
} else {
reject(new Error(response.error));
}
});

this.ws.send(JSON.stringify({
type: 'subscribe',
id,
channel,
params
}));
});
}

unsubscribe(subscriptionId) {
this.ws.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: subscriptionId
}));
this.subscriptions.delete(subscriptionId);
}

startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.connected) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 25000);
}

stopHeartbeat() {
clearInterval(this.heartbeatInterval);
}

generateId() {
return Math.random().toString(36).substring(2, 15);
}

close() {
this.stopHeartbeat();
this.ws.close();
}
}

// Usage
const ws = new OlympusWebSocket({
token: 'your-access-token',
onEvent: (event) => {
console.log('Received:', event);
}
});

await ws.connect();
await ws.subscribe('orders', { location_id: 'loc-xyz789' });

Flutter/Dart Client Example

import 'dart:convert';
import 'dart:async';
import 'package:web_socket_channel/web_socket_channel.dart';

class OlympusWebSocket {
final String url;
final String token;
final Function(Map<String, dynamic>) onEvent;

WebSocketChannel? _channel;
Timer? _heartbeatTimer;
final Map<String, Function> _handlers = {};

OlympusWebSocket({
this.url = 'wss://ws.olympuscloud.ai/v1',
required this.token,
required this.onEvent,
});

Future<void> connect() async {
_channel = WebSocketChannel.connect(
Uri.parse('$url?token=$token'),
);

_channel!.stream.listen(
(data) => _handleMessage(jsonDecode(data)),
onError: (error) => print('Error: $error'),
onDone: () => _stopHeartbeat(),
);

_startHeartbeat();
}

void _handleMessage(Map<String, dynamic> message) {
switch (message['type']) {
case 'ping':
_send({'type': 'pong'});
break;
case 'event':
onEvent(message);
break;
case 'response':
final handler = _handlers[message['id']];
if (handler != null) {
handler(message);
_handlers.remove(message['id']);
}
break;
}
}

Future<String> subscribe(String channel, Map<String, dynamic> params) async {
final completer = Completer<String>();
final id = _generateId();

_handlers[id] = (response) {
if (response['success']) {
completer.complete(response['subscription_id']);
} else {
completer.completeError(response['error']);
}
};

_send({
'type': 'subscribe',
'id': id,
'channel': channel,
'params': params,
});

return completer.future;
}

void _send(Map<String, dynamic> data) {
_channel?.sink.add(jsonEncode(data));
}

void _startHeartbeat() {
_heartbeatTimer = Timer.periodic(
Duration(seconds: 25),
(_) => _send({'type': 'ping'}),
);
}

void _stopHeartbeat() {
_heartbeatTimer?.cancel();
}

String _generateId() {
return DateTime.now().millisecondsSinceEpoch.toString();
}

void close() {
_stopHeartbeat();
_channel?.sink.close();
}
}

Error Codes

CodeDescription
invalid_tokenToken is invalid or expired
unauthorizedNot authorized for resource
rate_limitedToo many requests
connection_limitMax connections reached
subscription_limitMax subscriptions reached
invalid_messageMalformed message