LatteStream®
Quick Start
Getting Started
Building Custom SDKs
SDKs & Libraries
JavaScript/TypeScript
Node.js / Bun / Deno
Python
Go
PHP
API Reference
WebSocket API
REST API
Webhooks
Authentication
Complete protocol specification for building custom LatteStream SDKs
This document provides the complete WebSocket protocol specification for LatteStream. Whether you're building a custom SDK or integrating directly with the WebSocket API, this guide covers everything you need to know.
LatteStream uses a WebSocket protocol with enhanced performance and additional features. The protocol supports:
wss://ws-{cluster}.lattestream.com
Clusters: eu1
Immediately after the WebSocket connection opens, send an authentication message:
{ "api_key": "your-api-key-or-jwt-token" }
Supported key formats:
lspk_*): Requires discovery service (see Authentication)lsk_*): Direct encrypted API keys/apps/token endpointServer responds with connection confirmation:
{ "event": "lattestream:connection_established", "data": { "socket_id": "123.456", "activity_timeout": 120, "protocol": 7 } }
Socket ID Format: {tenant_id}.{connection_id}
The socket_id is used for channel authorization on private/presence channels.
All messages follow a standard JSON structure:
{ "event": "event_name", "channel": "channel_name", "data": { "key": "value" } }
Field Specifications:
event (required): Event name (max 200 characters)channel (optional): Channel name for subscriptions/broadcasts (max 164 characters)data (optional): Event payload (max 32KB){ "event": "lattestream:subscribe", "data": { "channel": "my-channel" } }
For private/presence channels, include auth token:
{ "event": "lattestream:subscribe", "data": { "channel": "private-chat", "auth": "lspc_encrypted_token" } }
Response (subscription succeeded):
{ "event": "lattestream_internal:subscription_succeeded", "channel": "my-channel", "data": {} }
Response (presence channel):
{ "event": "lattestream_internal:subscription_succeeded", "channel": "presence-room-1", "data": { "presence": { "ids": ["user1", "user2"], "hash": { "user1": { "name": "Alice" }, "user2": { "name": "Bob" } }, "count": 2 } } }
{ "event": "lattestream:unsubscribe", "data": { "channel": "my-channel" } }
Client sends:
{ "event": "lattestream:ping", "data": {} }
Server responds:
{ "event": "lattestream:pong", "data": {} }
Timing:
Naming: No prefix (e.g., my-channel, chat-room-1)
Characteristics:
Example:
{ "event": "lattestream:subscribe", "data": { "channel": "public-announcements" } }
Naming: private- prefix (e.g., private-user-123, private-chat)
Characteristics:
client-)Authorization Flow:
private-user-123POST /authSubscribe Example:
{ "event": "lattestream:subscribe", "data": { "channel": "private-chat-room", "auth": "lspc_abc123..." } }
Naming: presence- prefix (e.g., presence-room-1, presence-game-lobby)
Characteristics:
Subscribe Example:
{ "event": "lattestream:subscribe", "data": { "channel": "presence-game-lobby", "auth": "lspc_abc123..." } }
Presence Events:
Member joined:
{ "event": "lattestream_internal:member_added", "channel": "presence-room-1", "data": { "user_id": "user123", "user_info": { "name": "Alice", "avatar": "https://..." } } }
Member left:
{ "event": "lattestream_internal:member_removed", "channel": "presence-room-1", "data": { "user_id": "user123", "user_info": { "name": "Alice" } } }
Valid characters: Alphanumeric, hyphens, underscores
my-channel, user_123, chat-room-1Case sensitivity: Channel names are case-sensitive
My-Channel and my-channel are different channelsMaximum length: 164 characters
Reserved prefixes:
private-: Private channelspresence-: Presence channelslattestream:: Protocol messages (reserved)lattestream_internal:: Internal events (reserved)When a server triggers an event or a client sends a client event, you receive:
{ "event": "new-message", "channel": "chat-room", "data": { "user": "Alice", "message": "Hello, world!" } }
Requirements:
private-*) and presence (presence-*) channelsclient-Sending client event:
{ "event": "client-typing", "channel": "private-chat", "data": { "user": "Alice", "status": "typing" } }
Error response (if client event on public channel):
{ "event": "lattestream:error", "data": { "code": 4301, "message": "Client events are only allowed on private and presence channels" } }
Error response (if event doesn't start with client-):
{ "event": "lattestream:error", "data": { "code": 4201, "message": "Client events must be prefixed with 'client-'" } }
{ "event": "lattestream:error", "data": { "code": 4009, "message": "Unauthorized to access channel" } }
| Code | Name | Description | Action |
|---|---|---|---|
| 4001 | Generic subscription error | General subscription failure | Check channel name and auth |
| 4004 | Maximum channels exceeded | Too many subscriptions per client | Unsubscribe from unused channels |
| 4005 | Invalid channel name | Channel name format invalid | Check naming rules |
| 4009 | Unauthorized | Authentication failed | Verify auth token |
| 4201 | Invalid event | Client event must be prefixed with client- | Add client- prefix |
| 4301 | Client event rejected | Client events only on private/presence | Use private/presence channel |
Some errors use string codes in the code field:
subscription_limit: Too many channel subscriptionsinvalid_event: Event name validation failedclient_event_rejected: Client events not allowed on this channel typenot_subscribed: Attempted operation on unsubscribed channelunauthorized: Channel authorization failedchannel_not_found: Channel does not existinvalid_request: Request format is invalidPer Client:
LATTESTREAM_MAX_CHANNELS_PER_CLIENTPer Tenant:
Per Node:
Message Size:
Channel Buffer:
Throttling Behavior:
Queue Timeout: 5000ms
Activity Timeout: 120 seconds (default)
Ping Interval: 30 seconds (recommended)
Pong Timeout: 30 seconds
LatteStream supports optimized binary messages for performance-critical applications.
Frame Structure:
[4 bytes: type][4 bytes: length][payload]
Message Types:
0x01: JSON payload (UTF-8 encoded)0x02: Binary payload with JSON metadata0x03: Compressed JSON payloadNote: Most SDKs use text frames (JSON) by default. Binary protocol is optional for optimization.
For high-throughput scenarios, batch multiple messages:
{ "event": "lattestream:batch", "data": { "messages": [ { "event": "lattestream:subscribe", "data": { "channel": "channel-1" } }, { "event": "lattestream:subscribe", "data": { "channel": "channel-2" } } ] } }
Implement these connection states in your SDK:
connecting - Establishing WebSocket connectionconnected - Successfully connected and authenticateddisconnected - Connection lostunavailable - Connection unavailable (retry exhausted)failed - Connection failed permanentlyLatency:
Throughput:
Scalability:
When building a custom SDK, implement:
Connection Management
Reconnection Logic
Channel Management
Event Handling
Heartbeat
Error Handling
See the SDK Implementation Guide for detailed patterns and best practices from the official TypeScript/JavaScript SDK.
// 1. Open WebSocket connection const ws = new WebSocket('wss://ws-us-east-1.lattestream.com'); // 2. On connection open, send authentication ws.onopen = () => { ws.send( JSON.stringify({ api_key: 'lsk_your_private_token', }) ); }; // 3. Handle messages ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.event === 'lattestream:connection_established') { console.log('Connected! Socket ID:', message.data.socket_id); // 4. Subscribe to channel ws.send( JSON.stringify({ event: 'lattestream:subscribe', data: { channel: 'my-channel' }, }) ); } if (message.event === 'lattestream_internal:subscription_succeeded') { console.log('Subscribed to:', message.channel); } // Handle your custom events if (message.event === 'new-message') { console.log('Received:', message.data); } // Handle ping/pong if (message.event === 'lattestream:ping') { ws.send(JSON.stringify({ event: 'lattestream:pong', data: {} })); } }; // 5. Handle errors and disconnection ws.onerror = (error) => { console.error('WebSocket error:', error); }; ws.onclose = () => { console.log('Disconnected, attempting to reconnect...'); // Implement reconnection logic here };
Next Steps: Learn about Authentication to implement secure channel access in your custom SDK.