Skip to main content
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:
  • exchange
  • pair/instrument
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

  • Verify API key is valid and active
  • Check network connectivity and firewall settings
  • Ensure proper WebSocket library configuration
  • Verify endpoint URL is correct for data type

Subscription Issues

  • Confirm subscription parameters are valid
  • Check subscription limits haven’t been exceeded
  • Verify exchange and pair names are correct
  • Ensure proper JSON-RPC 2.0 format

Performance Issues

  • Monitor message processing latency
  • Check for memory leaks in message handling
  • Verify connection distribution across endpoints
  • Review subscription patterns for efficiency

Data Quality Issues

  • Implement message deduplication
  • Add timestamp validation
  • Monitor for missing sequence numbers
  • Verify data format expectations

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.