This guide covers advanced error handling, troubleshooting scenarios, and production-ready configuration for Amberdata’s WebSocket services.
Error Handling & Response Codes
This section describes common WebSocket subscription errors and how to resolve them.
Common Error Scenarios
1. Wildcards Not Supported for This Feature
Wildcard subscriptions are not supported for Tickers or Order Book Event streams.
Error Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"description": "not authorized to access this resource",
"code": 403
}
}
Cause: Attempting to subscribe to Tickers or Order Book Events without explicitly specifying both:
Solution:
// ❌ This will fail
{
"params": ["market:spot:tickers", {"exchange": "binance"}]
}
// ✅ Use explicit instrument subscriptions
{
"params": ["market:spot:tickers", {"exchange": "binance", "pair": "btc_usdt"}]
}
2. Invalid Subscription Error
Error Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"description": "subscription 'market:spot:tickers:snapsh000ts' is not supported",
"code": 400
}
}
Cause: Attempting to subscribe with non-existent or misspelled subscription name.
Solution:
// ❌ This will fail
{
"params": ["market:spot:tickers:snapsh000ts", {"exchange": "bitget", "pair": "btc_usdt"}]
}
// ✅ Use a valid subscription name
{
"params": ["market:spot:tickers:snapshots", {"exchange": "bitget", "pair": "btc_usdt"}]
}
3. Invalid API Key
Error: Connection closes with message invalid api key '<api_key>'
Solution: Verify API key and permissions:
const connectWithRetry = (apiKey, maxRetries = 3) => {
let retries = 0;
const connect = () => {
const ws = new WebSocket('wss://ws.amberdata.com', {
headers: {
'x-api-key': apiKey,
'x-amberdata-blockchain-id': 'ethereum-mainnet'
}
});
ws.on('error', (error) => {
if (error.message.includes('invalid api key') && retries < maxRetries) {
retries++;
console.log(`Retrying connection (${retries}/${maxRetries})...`);
setTimeout(connect, 1000 * retries); // Exponential backoff
} else {
console.error('Connection failed:', error);
}
});
return ws;
};
return connect();
};
4. Missing API Key
Error Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"description": "missing api key",
"code": 401
}
}
Cause: Attempting to connect/subscribe with a missing API key.
Solution:
// ❌ This will fail
const ws = new WebSocket('wss://ws.amberdata.com/spot');
// ✅ Must provide API key
const ws = new WebSocket('wss://ws.amberdata.com/spot', {
headers: { 'x-api-key': 'VALID_API_KEY_HERE' }
});
5. Missing Parameter(s)
Error Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"description": "params must contain either 1 or 2 elements",
"code": 400
}
}
Cause: Attempting to subscribe with no parameters.
Solution:
// ❌ This will fail
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscribe"
}
// ✅ Must provide params
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscribe",
"params": [
"market:spot:trades",
{
"pair": "btc_usd",
"exchange": "gdax"
}
]
}
Production Error Handling Patterns
Robust Reconnection Logic
class RobustWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
this.reconnectDelay = options.reconnectDelay || 1000;
this.subscriptions = new Map();
this.connect();
}
connect() {
try {
this.ws = new WebSocket(this.url, {
headers: this.options.headers
});
this.ws.on('open', () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.resubscribeAll();
});
this.ws.on('message', (data) => {
this.handleMessage(JSON.parse(data));
});
this.ws.on('close', () => {
console.log('WebSocket disconnected');
this.handleReconnect();
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.handleReconnect();
});
} catch (error) {
console.error('Connection failed:', error);
this.handleReconnect();
}
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => this.connect(), delay);
} else {
console.error('Max reconnection attempts reached');
}
}
subscribe(params) {
const id = Date.now();
const request = {
jsonrpc: "2.0",
id: id,
method: "subscribe",
params: params
};
// Store subscription for reconnection
this.subscriptions.set(id, params);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(request));
}
}
resubscribeAll() {
for (const [id, params] of this.subscriptions) {
this.subscribe(params);
}
}
handleMessage(message) {
if (message.error) {
console.error('Subscription error:', message.error);
this.handleSubscriptionError(message);
} else if (message.method === 'subscription') {
this.handleSubscriptionData(message.params);
}
}
handleSubscriptionError(message) {
const { error, id } = message;
switch (error.code) {
case 400:
if (error.description.includes('Wildcard subscription')) {
console.error('Wildcard subscription not allowed for:', id);
// Remove invalid subscription
this.subscriptions.delete(id);
} else if (error.description.includes('Max subscription limit')) {
console.error('Subscription limit reached');
// Implement connection pooling logic
this.handleSubscriptionLimit();
}
break;
default:
console.error('Unknown subscription error:', error);
}
}
}
Message Processing & Buffering
class MessageProcessor {
constructor(batchSize = 100, flushInterval = 1000) {
this.buffer = [];
this.batchSize = batchSize;
this.flushInterval = flushInterval;
this.processing = false;
// Flush buffer periodically
setInterval(() => this.flush(), flushInterval);
}
addMessage(message) {
this.buffer.push({
...message,
timestamp: Date.now()
});
if (this.buffer.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.processing || this.buffer.length === 0) return;
this.processing = true;
const batch = this.buffer.splice(0, this.batchSize);
try {
await this.processBatch(batch);
} catch (error) {
console.error('Batch processing failed:', error);
// Re-queue failed messages
this.buffer.unshift(...batch);
} finally {
this.processing = false;
}
}
async processBatch(messages) {
// Process messages in batch
const grouped = this.groupMessagesByType(messages);
for (const [type, msgs] of Object.entries(grouped)) {
await this.processMessageType(type, msgs);
}
}
groupMessagesByType(messages) {
return messages.reduce((groups, msg) => {
const type = this.getMessageType(msg);
if (!groups[type]) groups[type] = [];
groups[type].push(msg);
return groups;
}, {});
}
}
Advanced Configuration
Connection Optimization
Multiple Endpoint Strategy:
class MultiEndpointManager {
constructor() {
this.connections = {
spot: new RobustWebSocket('wss://ws.amberdata.com/spot', {
headers: { 'x-api-key': process.env.API_KEY }
}),
futures: new RobustWebSocket('wss://ws.amberdata.com/futures', {
headers: { 'x-api-key': process.env.API_KEY }
}),
options: new RobustWebSocket('wss://ws.amberdata.com/options', {
headers: { 'x-api-key': process.env.API_KEY }
}),
blockchain: new RobustWebSocket('wss://ws.amberdata.com', {
headers: {
'x-api-key': process.env.API_KEY,
'x-amberdata-blockchain-id': 'ethereum-mainnet'
}
})
};
}
subscribe(dataType, params) {
const endpoint = this.getEndpointForDataType(dataType);
if (endpoint) {
this.connections[endpoint].subscribe([dataType, params]);
} else {
throw new Error(`No endpoint configured for data type: ${dataType}`);
}
}
getEndpointForDataType(dataType) {
if (dataType.startsWith('market:spot:')) return 'spot';
if (dataType.startsWith('market:futures:')) return 'futures';
if (dataType.startsWith('market:options:')) return 'options';
if (dataType.includes('block') || dataType.includes('transaction')) return 'blockchain';
return null;
}
}
Health Monitoring
class ConnectionHealthMonitor {
constructor(connections) {
this.connections = connections;
this.metrics = {
messageCount: 0,
errorCount: 0,
lastMessageTime: Date.now(),
connectionStatus: {}
};
this.startHealthChecks();
}
startHealthChecks() {
// Check connection health every 30 seconds
setInterval(() => this.performHealthCheck(), 30000);
// Send ping every 10 seconds
setInterval(() => this.sendPings(), 10000);
}
performHealthCheck() {
const now = Date.now();
const timeSinceLastMessage = now - this.metrics.lastMessageTime;
// Alert if no messages in 60 seconds
if (timeSinceLastMessage > 60000) {
console.warn('No messages received in 60 seconds');
this.triggerReconnection();
}
// Log health metrics
console.log('Health Status:', {
messageCount: this.metrics.messageCount,
errorCount: this.metrics.errorCount,
timeSinceLastMessage: timeSinceLastMessage,
activeConnections: Object.keys(this.connections).length
});
}
sendPings() {
for (const [name, connection] of Object.entries(this.connections)) {
if (connection.ws && connection.ws.readyState === WebSocket.OPEN) {
connection.ws.ping();
}
}
}
recordMessage() {
this.metrics.messageCount++;
this.metrics.lastMessageTime = Date.now();
}
recordError() {
this.metrics.errorCount++;
}
}
Enterprise Features & Customization
Custom Rate Limits
Enterprise customers can request custom configurations:
// Example enterprise configuration request
const enterpriseConfig = {
maxConnections: 100,
maxSubscriptionsPerConnection: 500,
wildcardSupport: true,
customEndpoints: true,
prioritySupport: true,
dedicatedInfrastructure: true
};
// Contact sales team with requirements
Advanced Subscription Patterns
Conditional Subscriptions:
class ConditionalSubscriber {
constructor(websocket) {
this.ws = websocket;
this.conditions = new Map();
}
subscribeWithCondition(params, condition) {
const subscriptionId = this.ws.subscribe(params);
this.conditions.set(subscriptionId, condition);
}
handleMessage(message) {
const { subscription, result } = message.params;
const condition = this.conditions.get(subscription);
if (condition && condition(result)) {
this.processMessage(result);
}
}
}
Troubleshooting Checklist
Connection Issues
Subscription Issues
Data Quality Issues
Getting Support
For enterprise-level support and custom configurations:
- Technical Issues: Contact support with connection logs and error messages
- Custom Rate Limits: Reach out to sales team with requirements
- Performance Optimization: Schedule consultation for high-volume use cases
- Integration Support: Request dedicated technical account management
Enterprise customers receive priority support with guaranteed response times and dedicated infrastructure options.