WebSocket Error Handling
Comprehensive guide to handling WebSocket errors, including connection issues, message errors, and recovery strategies.
Error Categories
Connection Errors
Errors related to WebSocket connection establishment and maintenance.
Authentication Errors
Errors related to API authentication and authorization.
Message Errors
Errors related to message parsing, validation, and processing.
Subscription Errors
Errors related to channel subscriptions and data access.
Connection Errors
Connection Refused
Error Code: ECONNREFUSED
Description: Server is not accepting connections.
Possible Causes:
- Server is down or restarting
- Network connectivity issues
- Firewall blocking connections
- Incorrect WebSocket URL
Handling:
ws.on("error", (error) => {
if (error.code === "ECONNREFUSED") {
console.log("Connection refused - server may be down");
// Implement exponential backoff retry
scheduleReconnect();
}
});
DNS Resolution Failed
Error Code: ENOTFOUND
Description: Cannot resolve WebSocket hostname.
Possible Causes:
- Incorrect hostname
- DNS server issues
- Network connectivity problems
Handling:
ws.on("error", (error) => {
if (error.code === "ENOTFOUND") {
console.log("DNS resolution failed");
// Check hostname and network connectivity
validateConnection();
}
});
Connection Timeout
Error Code: ETIMEDOUT
Description: Connection attempt timed out.
Possible Causes:
- Slow network connection
- Server overload
- Firewall delays
Handling:
ws.on("error", (error) => {
if (error.code === "ETIMEDOUT") {
console.log("Connection timeout");
// Retry with longer timeout
retryWithLongerTimeout();
}
});
Connection Closed Unexpectedly
Error Code: ECONNABORTED
Description: Connection was closed unexpectedly.
Possible Causes:
- Network interruption
- Server restart
- Authentication failure
Handling:
ws.on("close", (code, reason) => {
console.log(`Connection closed: ${code} ${reason}`);
if (code === 1006) {
// Abnormal closure - attempt reconnection
scheduleReconnect();
} else if (code === 1000) {
// Normal closure
console.log("Connection closed normally");
}
});
Authentication Errors
Invalid API Key
Error Code: invalid_api_key
Description: The provided API key is invalid or expired.
Response:
{
"type": "auth_error",
"error": "invalid_api_key",
"message": "The provided API key is invalid or expired",
"code": 401
}
Handling:
function handleMessage(message) {
if (message.type === "auth_error" && message.error === "invalid_api_key") {
console.error("Invalid API key");
// Prompt user to update API credentials
promptForNewCredentials();
}
}
Invalid Signature
Error Code: invalid_signature
Description: The request signature is invalid.
Response:
{
"type": "auth_error",
"error": "invalid_signature",
"message": "The request signature is invalid",
"code": 401
}
Handling:
function handleMessage(message) {
if (message.type === "auth_error" && message.error === "invalid_signature") {
console.error("Invalid signature");
// Check signature generation logic
validateSignatureGeneration();
}
}
Timestamp Too Old
Error Code: timestamp_too_old
Description: The request timestamp is outside the acceptable time window.
Response:
{
"type": "auth_error",
"error": "timestamp_too_old",
"message": "The request timestamp is outside the acceptable time window",
"code": 401
}
Handling:
function handleMessage(message) {
if (message.type === "auth_error" && message.error === "timestamp_too_old") {
console.error("Timestamp too old");
// Sync system time or regenerate timestamp
syncSystemTime();
}
}
Insufficient Permissions
Error Code: insufficient_permissions
Description: API key does not have required permissions.
Response:
{
"type": "auth_error",
"error": "insufficient_permissions",
"message": "Your API key does not have permission to access this resource",
"code": 403
}
Handling:
function handleMessage(message) {
if (
message.type === "auth_error" &&
message.error === "insufficient_permissions"
) {
console.error("Insufficient permissions");
// Check API key permissions or upgrade account
checkPermissions();
}
}
Message Errors
Invalid Message Format
Error Code: invalid_message_format
Description: Message does not conform to expected format.
Response:
{
"type": "error",
"error": "invalid_message_format",
"message": "Message format is invalid",
"code": 400
}
Handling:
function sendMessage(message) {
try {
// Validate message format before sending
validateMessage(message);
ws.send(JSON.stringify(message));
} catch (error) {
console.error("Invalid message format:", error);
// Fix message format and retry
fixMessageFormat(message);
}
}
Message Too Large
Error Code: message_too_large
Description: Message exceeds size limit.
Response:
{
"type": "error",
"error": "message_too_large",
"message": "Message exceeds maximum size limit of 64KB",
"code": 413
}
Handling:
function sendMessage(message) {
const messageSize = JSON.stringify(message).length;
if (messageSize > 65536) {
// 64KB
console.error("Message too large");
// Split message or reduce data
splitMessage(message);
} else {
ws.send(JSON.stringify(message));
}
}
Invalid JSON
Error Code: invalid_json
Description: Message contains invalid JSON.
Response:
{
"type": "error",
"error": "invalid_json",
"message": "Message contains invalid JSON",
"code": 400
}
Handling:
function handleMessage(rawMessage) {
try {
const message = JSON.parse(rawMessage);
processMessage(message);
} catch (error) {
console.error("Invalid JSON:", error);
console.log("Raw message:", rawMessage);
// Log error and continue
}
}
Subscription Errors
Invalid Symbol
Error Code: invalid_symbol
Description: The trading pair symbol is not supported.
Response:
{
"type": "subscription_error",
"channel": "ticker",
"symbol": "INVALID/USD",
"error": "invalid_symbol",
"message": "The symbol 'INVALID/USD' is not supported",
"code": 400
}
Handling:
function handleMessage(message) {
if (
message.type === "subscription_error" &&
message.error === "invalid_symbol"
) {
console.error(`Invalid symbol: ${message.symbol}`);
// Remove invalid symbol from subscriptions
removeInvalidSymbol(message.symbol);
}
}
Channel Not Available
Error Code: channel_not_available
Description: The requested channel is not available.
Response:
{
"type": "subscription_error",
"channel": "invalid_channel",
"error": "channel_not_available",
"message": "The channel 'invalid_channel' is not available",
"code": 400
}
Handling:
function handleMessage(message) {
if (
message.type === "subscription_error" &&
message.error === "channel_not_available"
) {
console.error(`Channel not available: ${message.channel}`);
// Use alternative channel or fallback
useAlternativeChannel(message.channel);
}
}
Subscription Limit Exceeded
Error Code: subscription_limit_exceeded
Description: Maximum number of subscriptions reached.
Response:
{
"type": "subscription_error",
"error": "subscription_limit_exceeded",
"message": "Maximum number of subscriptions (50) reached",
"code": 429
}
Handling:
function handleMessage(message) {
if (
message.type === "subscription_error" &&
message.error === "subscription_limit_exceeded"
) {
console.error("Subscription limit exceeded");
// Unsubscribe from least important channels
optimizeSubscriptions();
}
}
Rate Limiting Errors
Rate Limit Exceeded
Error Code: rate_limit_exceeded
Description: Too many messages sent too quickly.
Response:
{
"type": "error",
"error": "rate_limit_exceeded",
"message": "Too many messages. Please slow down.",
"code": 429,
"retryAfter": 60
}
Handling:
function handleMessage(message) {
if (message.type === "error" && message.error === "rate_limit_exceeded") {
console.error("Rate limit exceeded");
const retryAfter = message.retryAfter || 60;
// Implement backoff strategy
implementBackoff(retryAfter);
}
}
Error Recovery Strategies
Exponential Backoff
class ReconnectionManager {
constructor() {
this.attempts = 0;
this.maxAttempts = 10;
this.baseDelay = 1000; // 1 second
this.maxDelay = 30000; // 30 seconds
}
scheduleReconnect() {
if (this.attempts >= this.maxAttempts) {
console.error("Max reconnection attempts reached");
return;
}
this.attempts++;
const delay = Math.min(
this.baseDelay * Math.pow(2, this.attempts - 1),
this.maxDelay
);
console.log(`Reconnecting in ${delay}ms (attempt ${this.attempts})`);
setTimeout(() => {
this.connect();
}, delay);
}
resetAttempts() {
this.attempts = 0;
}
}
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.threshold = threshold;
this.timeout = timeout;
this.failureCount = 0;
this.lastFailureTime = null;
this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
}
canExecute() {
if (this.state === "CLOSED") {
return true;
}
if (this.state === "OPEN") {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = "HALF_OPEN";
return true;
}
return false;
}
if (this.state === "HALF_OPEN") {
return true;
}
return false;
}
onSuccess() {
this.failureCount = 0;
this.state = "CLOSED";
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) {
this.state = "OPEN";
}
}
}
Message Queue for Failed Messages
class MessageQueue {
constructor() {
this.queue = [];
this.maxSize = 1000;
}
enqueue(message) {
if (this.queue.length >= this.maxSize) {
// Remove oldest message
this.queue.shift();
}
this.queue.push({
message,
timestamp: Date.now(),
attempts: 0,
});
}
dequeue() {
return this.queue.shift();
}
isEmpty() {
return this.queue.length === 0;
}
retryFailedMessages() {
const now = Date.now();
const retryDelay = 5000; // 5 seconds
this.queue.forEach((item) => {
if (now - item.timestamp > retryDelay && item.attempts < 3) {
item.attempts++;
this.sendMessage(item.message);
}
});
}
}
Error Monitoring and Logging
Comprehensive Error Logging
class ErrorLogger {
constructor() {
this.errors = [];
this.maxErrors = 1000;
}
logError(error, context = {}) {
const errorEntry = {
timestamp: new Date().toISOString(),
error: {
message: error.message,
code: error.code,
stack: error.stack,
},
context,
userAgent: navigator.userAgent,
url: window.location.href,
};
this.errors.push(errorEntry);
if (this.errors.length > this.maxErrors) {
this.errors.shift();
}
// Send to monitoring service
this.sendToMonitoring(errorEntry);
}
sendToMonitoring(errorEntry) {
// Send to external monitoring service
fetch("/api/errors", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(errorEntry),
}).catch((err) => {
console.error("Failed to send error to monitoring:", err);
});
}
}
Error Metrics
class ErrorMetrics {
constructor() {
this.metrics = {
connectionErrors: 0,
authenticationErrors: 0,
messageErrors: 0,
subscriptionErrors: 0,
rateLimitErrors: 0,
};
}
incrementError(type) {
if (this.metrics[type] !== undefined) {
this.metrics[type]++;
}
}
getMetrics() {
return {
...this.metrics,
totalErrors: Object.values(this.metrics).reduce(
(sum, count) => sum + count,
0
),
};
}
}
Best Practices
Error Handling Checklist
- ✅ Implement comprehensive error logging
- ✅ Use exponential backoff for reconnections
- ✅ Validate all incoming messages
- ✅ Handle rate limiting gracefully
- ✅ Implement circuit breaker pattern
- ✅ Monitor error metrics
- ✅ Provide user-friendly error messages
- ✅ Test error scenarios thoroughly
Error Recovery Patterns
- Retry Logic: Implement smart retry with backoff
- Fallback Mechanisms: Use alternative data sources
- Graceful Degradation: Continue operation with reduced functionality
- User Notification: Inform users of issues and recovery status
- Health Checks: Monitor system health and connectivity
Need help with WebSocket connections? Check out our Connection Guide or explore Event Handling.