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:
| Feature | Description |
|---|---|
| Order Updates | Real-time order status changes |
| Kitchen Events | KDS ticket updates |
| Inventory Alerts | Low stock notifications |
| Analytics | Live dashboard metrics |
| Voice AI | Streaming audio sessions |
Connection URL
wss://ws.olympuscloud.ai/v1
Environment URLs
| Environment | URL |
|---|---|
| Production | wss://ws.olympuscloud.ai/v1 |
| Staging | wss://ws.staging.olympuscloud.ai/v1 |
| Development | wss://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
| State | Description |
|---|---|
connecting | Establishing connection |
authenticating | Sending credentials |
connected | Ready to send/receive |
reconnecting | Attempting reconnection |
disconnected | Connection 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
| Limit | Value |
|---|---|
| Max connections per user | 10 |
| Max subscriptions per connection | 50 |
| Max message size | 64KB |
| 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
| Code | Description |
|---|---|
invalid_token | Token is invalid or expired |
unauthorized | Not authorized for resource |
rate_limited | Too many requests |
connection_limit | Max connections reached |
subscription_limit | Max subscriptions reached |
invalid_message | Malformed message |
Related Documentation
- WebSocket Events - Event types
- WebSocket Subscriptions - Subscribe to channels
- Authentication - Get access tokens