WebSocket Connection
Relay uses persistent WebSocket connections for real-time communication. Your app connects once with its Bearer token and keeps the connection open to send events and receive replies.
Connection Details
Endpoint: wss://api.relay.ckgworks.com/v1/ws/app
Authentication: Bearer token in the Authorization header
Authorization: Bearer rlk_yourapp_x8k2m9p...
Connection Lifecycle
1. Initial Connection
When you connect, send the Bearer token in the WebSocket handshake header:
import asyncio
import websockets
import json
async def connect_to_relay():
token = "rlk_yourapp_x8k2m9p..."
uri = "wss://api.relay.ckgworks.com/v1/ws/app"
headers = {
"Authorization": f"Bearer {token}"
}
async with websockets.connect(uri, subprotocols=["chat"]) as websocket:
# Connection established
await handle_connection(websocket)
Relay validates your token on the handshake. If the token is invalid or revoked, the connection is rejected immediately.
2. Keeping the Connection Alive
Once connected, maintain the connection with periodic heartbeats:
async def send_heartbeat(websocket):
while True:
await asyncio.sleep(30) # Send every 30 seconds
ping_message = json.dumps({"type": "ping"})
await websocket.send(ping_message)
# Wait for pong response
try:
response = await asyncio.wait_for(websocket.recv(), timeout=5)
pong = json.loads(response)
if pong.get("type") != "pong":
print("Warning: expected pong, got", pong.get("type"))
except asyncio.TimeoutError:
print("No pong received, connection may be stale")
Relay responds with a {"type": "pong"} message. If you don't receive a response within a few seconds, consider the connection unhealthy.
3. Handling Disconnections
Always implement reconnection logic with exponential backoff:
async def connect_with_retry(token, max_retries=5):
retry_count = 0
base_delay = 1 # Start with 1 second
while retry_count < max_retries:
try:
uri = "wss://api.relay.ckgworks.com/v1/ws/app"
headers = {"Authorization": f"Bearer {token}"}
async with websockets.connect(uri, subprotocols=["chat"]) as websocket:
print(f"Connected successfully (attempt {retry_count + 1})")
retry_count = 0 # Reset on successful connection
await handle_connection(websocket)
except Exception as e:
print(f"Connection failed: {e}")
retry_count += 1
if retry_count >= max_retries:
print(f"Max retries ({max_retries}) exceeded")
break
# Exponential backoff: 1s, 2s, 4s, 8s, 16s (capped)
delay = min(base_delay * (2 ** (retry_count - 1)), 60)
print(f"Retrying in {delay} seconds...")
await asyncio.sleep(delay)
Exponential Backoff Strategy
Recommended exponential backoff parameters:
- Initial delay: 1 second
- Max delay: 60 seconds
- Multiplier: 2x per retry
- Jitter: Optional, add ±10% randomness to avoid thundering herd
import random
def calculate_backoff(retry_count, base_delay=1, max_delay=60):
delay = min(base_delay * (2 ** retry_count), max_delay)
# Add jitter: ±10%
jitter = delay * 0.1 * (random.random() - 0.5)
return delay + jitter
Node.js Example (ws library)
const WebSocket = require('ws');
const token = 'rlk_yourapp_x8k2m9p...';
const uri = 'wss://api.relay.ckgworks.com/v1/ws/app';
const ws = new WebSocket(uri, {
headers: {
Authorization: `Bearer ${token}`
}
});
ws.on('open', () => {
console.log('Connected to Relay');
// Start heartbeat
const heartbeatInterval = setInterval(() => {
ws.send(JSON.stringify({ type: 'ping' }));
}, 30 * 1000);
ws.on('close', () => clearInterval(heartbeatInterval));
});
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'pong') {
console.log('Pong received');
} else if (message.type === 'token') {
console.log('Token:', message.token);
} else if (message.type === 'reply') {
console.log('Full reply:', message.reply);
}
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
ws.on('close', () => {
console.log('Connection closed, reconnecting...');
setTimeout(() => connectWithRetry(), 1000);
});
// Reconnection logic
let retryCount = 0;
const maxRetries = 5;
function connectWithRetry() {
if (retryCount >= maxRetries) {
console.error('Max retries exceeded');
return;
}
try {
const newWs = new WebSocket(uri, {
headers: { Authorization: `Bearer ${token}` }
});
newWs.on('open', () => {
console.log('Reconnected');
retryCount = 0;
});
newWs.on('close', connectWithRetry);
} catch (error) {
const delay = Math.min(1000 * Math.pow(2, retryCount), 60000);
retryCount++;
setTimeout(connectWithRetry, delay);
}
}
Connection Best Practices
Do
- Keep one persistent connection open per app instance
- Send heartbeats every 30-60 seconds
- Use exponential backoff for reconnection
- Log connection state changes
- Handle all message types (token, reply, error)
Don't
- Create a new connection for each event
- Ignore disconnection events
- Use fixed delays for retries
- Hardcode timeouts shorter than 5 seconds
- Send custom heartbeat patterns
Troubleshooting
Connection rejected immediately?
- Check your Bearer token is valid (prefix
rlk_) - Verify the token hasn't been rotated or revoked
- Confirm you're using the correct WebSocket endpoint
Connection drops frequently?
- Add more aggressive heartbeats (every 15-30 seconds)
- Check your network for packet loss
- Ensure exponential backoff is implemented correctly
Pong never arrives?
- Increase timeout from 5 to 10 seconds initially
- Verify network connectivity to
api.relay.ckgworks.com - Check firewall/proxy isn't filtering WebSocket frames