Connection & Errors
Connection Management
Connection Lifecycle
Important: WebSocket client sessions have a maximum duration of 1 hour. For long-running applications, you must implement reconnection logic to maintain persistent data streams. There is no WebSocket-level ping/pong keepalive mechanism built into the server. It is your responsibility as the client to handle reconnection for long-living sessions.
The REST
POST /heartbeatendpoint is a separate feature for automatic order cancellation (dead man's switch) and does not affect WebSocket connection lifecycle.
Error Handling
Error responses follow this format:
{
"kind": "response",
"type": "error",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "Description of the error"
}Common error scenarios:
-
Authentication failures:
"Failed to validate api key"- Generic validation failure"Not Authorized"- Not authenticated"Session already authorized"- Already authenticated- Specific codes:
NOT_FOUND,FORBIDDEN,KEY_USAGE_EXCEEDED,RATELIMITED - See the API Key Guide for troubleshooting.
-
Invalid subscriptions:
"Invalid subscription parameters""Invalid query for [CHANNEL] channel"
-
Invalid messages:
"invalid [MESSAGE_TYPE] payload [error_details]"
Error Handling Example
function handleWebSocketError(error: { type: string; success: boolean; error: string }) {
if (
error.error.includes('Failed to validate api key') ||
error.error === 'NOT_FOUND' ||
error.error === 'FORBIDDEN' ||
error.error === 'KEY_USAGE_EXCEEDED'
) {
console.error('API key error:', error.error);
} else if (error.error === 'RATELIMITED') {
console.warn('Rate limited. Implementing backoff...');
} else if (error.error === 'Not Authorized') {
console.warn('Not authenticated. Attempting to authenticate...');
} else if (error.error === 'Session already authorized') {
console.info('Session already authenticated');
} else if (error.error.includes('Invalid subscription parameters')) {
console.error('Invalid subscription:', error.error);
} else {
console.error('Unhandled WebSocket error:', error.error);
}
}Rate Limiting
The WebSocket API implements per-session rate limiting to ensure fair usage and system stability.
Rate Limits
| Message Type | Rate Limit | Window | Scope |
|---|---|---|---|
auth | 1 request | 1 second | Per client IP address |
get_instruments | 1 request | 1 second | Per API key owner |
get_subscriptions | 1 request | 1 second | Per API key owner |
logout | 1 request | 1 second | Per API key owner |
subscribe | 10 requests | 1 second | Per API key owner |
unsubscribe | 10 requests | 1 second | Per API key owner |
unsubscribe_all | 1 request | 1 second | Per API key owner |
get_ob_state_by_instruments | 5 requests | 20 seconds | Per API key owner |
get_ob_state_by_market | 5 requests | 20 seconds | Per API key owner |
resend | 5 requests | 10 seconds | Per API key owner |
Rate limits use a token bucket algorithm. All sessions for the same API key owner share rate limits. The auth request is rate-limited per client IP address before API key verification.
Rate Limit Response
{
"kind": "response",
"type": "error",
"id": "your-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": {
"type": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded for endpoint 'get_ob_state_by_instruments'. Limit: 5 requests per 20 seconds.",
"data": {
"remainingRequests": 0,
"resetTime": 1677721623000
}
}
}| Field | Description |
|---|---|
error.type | "RATE_LIMIT_EXCEEDED" |
error.message | Human-readable description |
error.data.remainingRequests | Remaining requests in current window |
error.data.resetTime | Unix timestamp (ms) when the rate limit resets |
Rate Limit Handler
function handleRateLimitedResponse(response: any) {
if (!response.success && response.error?.type === 'RATE_LIMIT_EXCEEDED') {
const resetTime = response.error.data?.resetTime;
const now = Date.now();
const waitMs = resetTime ? Math.max(0, resetTime - now) : 20000;
console.warn(`Rate limited. Waiting ${Math.ceil(waitMs / 1000)} seconds...`);
setTimeout(() => {
console.log('Rate limit window reset. Safe to retry.');
}, waitMs);
}
}Instead of polling with
get_ob_state_*, use channel subscriptions for real-time updates (no rate limit on event delivery).
Reconnection Strategy
class WebSocketClient {
private ws: WebSocket | null = null;
private readonly url: string;
private readonly apiKey: string;
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private reconnectInterval: number = 1000;
private subscriptions: Array<{
channel: string;
query: Record<string, any>;
}> = [];
private isAuthenticated: boolean = false;
constructor(url: string, apiKey: string) {
this.url = url;
this.apiKey = apiKey;
}
connect(): void {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected to WebSocket');
this.reconnectAttempts = 0;
this.reconnectInterval = 1000;
this.authenticate();
};
this.ws.onclose = (event) => {
console.log(`Connection closed: ${event.code} - ${event.reason}`);
this.isAuthenticated = false;
if (this.reconnectAttempts < this.maxReconnectAttempts) {
console.log(
`Reconnecting in ${this.reconnectInterval}ms (attempt ${this.reconnectAttempts + 1}/${this.maxReconnectAttempts})`
);
setTimeout(() => {
this.reconnectAttempts++;
this.reconnectInterval = Math.min(this.reconnectInterval * 2, 30000);
this.connect();
}, this.reconnectInterval);
} else {
console.error('Max reconnection attempts reached.');
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
}
private authenticate(): void {
if (!this.ws) return;
this.ws.send(JSON.stringify({ type: 'auth', api_key: this.apiKey }));
}
private handleMessage(message: any): void {
if (message.type === 'auth' && message.success) {
console.log('Authenticated successfully');
this.isAuthenticated = true;
if (this.subscriptions.length > 0) {
this.resubscribe();
}
} else if (message.type === 'auth' && !message.success) {
console.error('Authentication failed:', message.error);
} else if (message.event) {
console.log(`Event received: ${message.event}`, message.data);
}
}
subscribe(
subscriptions: Array<{ channel: string; query: Record<string, any> }>
): void {
if (!this.isAuthenticated) {
console.warn('Cannot subscribe: not authenticated');
return;
}
this.subscriptions = [...this.subscriptions, ...subscriptions];
this.ws?.send(JSON.stringify({ type: 'subscribe', subscriptions }));
}
private resubscribe(): void {
if (this.subscriptions.length > 0) {
console.log('Resubscribing after reconnection...');
this.ws?.send(JSON.stringify({
type: 'subscribe',
subscriptions: this.subscriptions
}));
}
}
disconnect(): void {
if (this.ws) {
this.reconnectAttempts = this.maxReconnectAttempts;
this.ws.send(JSON.stringify({ type: 'logout' }));
this.ws.close();
this.ws = null;
this.isAuthenticated = false;
}
}
forceReconnect(): void {
this.reconnectAttempts = 0;
this.reconnectInterval = 1000;
if (this.ws) {
this.ws.close();
}
this.connect();
}
}
// Usage
const client = new WebSocketClient('wss://staging.kyan.sh/ws', 'your-api-key');
client.connect();
setTimeout(() => {
client.subscribe([
{ channel: 'index_price', query: { pair: 'BTC_USDC' } }
]);
}, 2000);Best Practices
-
Connection Management:
- Always authenticate immediately after connecting
- Implement robust reconnection logic with exponential backoff - connections timeout after 1 hour
- Handle connection drops gracefully and automatically
- Store subscription state for seamless reconnection
- Monitor connection health and implement manual reconnection triggers
-
Subscription Management:
- Keep track of active subscriptions for reconnection
- Automatically resubscribe after successful reconnection
- Unsubscribe from channels you no longer need to reduce bandwidth
- Validate subscription success before considering them active
-
Error Handling:
- Implement comprehensive error handling for all message types
- Log errors for debugging and monitoring
- Handle rate limiting with exponential backoff
- Differentiate between recoverable and non-recoverable errors
-
Performance:
- Batch subscriptions when possible to reduce overhead
- Process messages efficiently to avoid blocking
- Consider using worker threads for heavy message processing
- Implement message queuing during reconnection periods
-
Security:
- Never expose API keys in client-side code
- Use secure WebSocket connections (wss://) only
- Validate all incoming data before processing
- Implement proper authentication state management
-
Reliability:
- Plan for regular disconnections - client sessions expire after 1 hour
- Implement connection health monitoring and automatic reconnection
- Use proper logging to track connection lifecycle events
- Test reconnection logic thoroughly in development
Updated 1 day ago
