MMTMMT Docs
WebSocket

Connection Lifecycle

WebSocket connection states, close codes, ping/pong heartbeat, reconnection strategies, and state recovery after reconnect.

Understanding the WebSocket connection lifecycle helps you build robust clients that handle all connection states gracefully.

Connection States

A WebSocket connection transitions through these states:

StateDescription
ConnectingTCP handshake and WebSocket upgrade in progress
OpenConnection established, ready for messaging
ClosingClose frame sent, waiting for acknowledgment
ClosedConnection terminated
Connecting ──► Open ──► Closing ──► Closed
                │                      ▲
                └──────────────────────┘
                    (on error/timeout)

Close Codes

When the server closes a connection, it sends a close code indicating the reason.

Standard Close Codes

CodeNameDescription
1000Normal ClosureClean shutdown, no error
1001Going AwayServer shutting down or restarting
1008Policy ViolationAuthentication failure or limit exceeded
1011Internal ErrorUnexpected server error

Interpreting Close Codes

ws.onclose = (event) => {
  switch (event.code) {
    case 1000:
      console.log('Connection closed normally');
      break;
    case 1001:
      console.log('Server going away, reconnect shortly');
      scheduleReconnect();
      break;
    case 1008:
      console.error('Policy violation:', event.reason);
      // Check reason for specific cause
      break;
    case 1011:
      console.error('Server error, will retry');
      scheduleReconnect();
      break;
    default:
      console.error('Unexpected close:', event.code, event.reason);
  }
};

Listing Subscriptions

You can request a list of your current subscriptions at any time:

Request:

{
  "type": "list_subscriptions"
}

Response:

{
  "type": "subscriptions",
  "count": 3,
  "max": 100,
  "subscriptions": [
    {
      "id": "candles.binancef.btc/usd.60",
      "channel": "candles",
      "exchange": "binancef",
      "symbol": "btc/usd",
      "tf": "1m"
    },
    {
      "id": "trades.binancef.eth/usd",
      "channel": "trades",
      "exchange": "binancef",
      "symbol": "eth/usd"
    },
    {
      "id": "vd.bybitf.btc/usd.60.1",
      "channel": "vd",
      "exchange": "bybitf",
      "symbol": "btc/usd",
      "tf": "1m",
      "bucket": 1
    }
  ]
}
FieldTypeDescription
typestringAlways "subscriptions"
countintNumber of active subscriptions
maxintMaximum subscriptions allowed
subscriptionsarrayList of subscription details

Server-Initiated Disconnections

The server may close your connection for these reasons:

API Key Revoked

If your API key is revoked or disabled while connected, the server closes the connection with code 1008.

Max Connections Exceeded

Each API key has a connection limit. Exceeding it results in the oldest connection being closed with code 1008 and reason max connections exceeded.

Backpressure (Slow Client)

If your client cannot consume messages fast enough, the server's send buffer fills up. Rather than consume unbounded memory, the server closes slow connections with code 1008 and reason backpressure.

Prevention:

  • Process messages asynchronously
  • Don't block the message handler
  • Subscribe only to channels you need
  • Consider reducing subscription count if issues persist

Disconnecting Message

Before closing a connection, the server sends a disconnecting message with the reason:

{
  "type": "disconnecting",
  "reason": "backpressure"
}
ReasonDescription
backpressureClient too slow to consume messages
timeoutConnection idle too long
server_shutdownServer is restarting or shutting down
normalClean client-initiated disconnect

This message is sent immediately before the WebSocket close frame, giving your client a chance to log or handle the disconnect reason.

Server Maintenance

During deployments or restarts, you receive code 1001 (Going Away). Reconnect after a brief delay.

Ping/Pong Heartbeat

The server supports two types of heartbeat mechanisms:

Protocol-Level Ping/Pong

The server sends WebSocket ping frames periodically to detect dead connections.

  1. Server sends ping frame every 30 seconds
  2. Client must respond with pong frame
  3. If no pong received within timeout, server assumes connection dead
  4. Server closes the connection

Most WebSocket libraries handle protocol-level ping/pong automatically:

// Node.js ws library - automatic
const ws = new WebSocket(url);
// Pong is sent automatically

// Browser WebSocket API - automatic
// No action needed, browser handles it
# Python websockets - automatic
# Pong frames are sent automatically

# If using a library that doesn't auto-respond:
async for message in websocket:
    if isinstance(message, websockets.frames.Ping):
        await websocket.pong(message.data)
// gorilla/websocket - must configure
conn.SetPongHandler(func(string) error {
    conn.SetReadDeadline(time.Now().Add(60 * time.Second))
    return nil
})

Application-Level Ping/Pong

For additional reliability, you can also use application-level ping/pong messages:

Ping Command:

{
  "type": "ping"
}

Pong Response:

{
  "type": "pong"
}

This is useful for:

  • Libraries that don't expose protocol-level ping/pong
  • Measuring application-level latency
  • Keeping connections alive through proxies that may not forward WebSocket frames

Reconnection Strategy

Implement exponential backoff to avoid overwhelming the server during outages.

class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.minDelay = options.minDelay || 1000;
    this.maxDelay = options.maxDelay || 30000;
    this.factor = options.factor || 2;
    this.currentDelay = this.minDelay;
    this.subscriptions = new Set();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.currentDelay = this.minDelay; // Reset backoff
      this.resubscribe(); // Restore subscriptions after reconnect
    };

    this.ws.onclose = (event) => {
      if (event.code !== 1000) {
        this.scheduleReconnect();
      }
    };

    this.ws.onerror = () => {
      // onclose will fire after this
    };
  }

  scheduleReconnect() {
    const jitter = Math.random() * 0.3 * this.currentDelay;
    const delay = this.currentDelay + jitter;

    console.log(`Reconnecting in ${Math.round(delay)}ms`);

    setTimeout(() => this.connect(), delay);

    // Increase delay for next attempt
    this.currentDelay = Math.min(
      this.currentDelay * this.factor,
      this.maxDelay
    );
  }

  resubscribe() {
    for (const channel of this.subscriptions) {
      this.ws.send(JSON.stringify({
        type: 'subscribe',
        channel: channel
      }));
    }
  }

  subscribe(channel) {
    this.subscriptions.add(channel);
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'subscribe',
        channel: channel
      }));
    }
  }

  unsubscribe(channel) {
    this.subscriptions.delete(channel);
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'unsubscribe',
        channel: channel
      }));
    }
  }
}

Backoff Schedule

AttemptDelay (base)With Jitter (typical)
11s1.0 - 1.3s
22s2.0 - 2.6s
34s4.0 - 5.2s
48s8.0 - 10.4s
516s16.0 - 20.8s
6+30s (max)30.0 - 39.0s

Handling Network Drops

Network disconnections don't always produce clean close events. Implement additional detection.

Client-Side Timeout

If you expect regular data but receive nothing, the connection may be dead:

class ConnectionMonitor {
  constructor(ws, timeout = 60000) {
    this.ws = ws;
    this.timeout = timeout;
    this.timer = null;
  }

  start() {
    this.resetTimer();
  }

  // Call this whenever you receive any message
  onMessage() {
    this.resetTimer();
  }

  resetTimer() {
    clearTimeout(this.timer);
    this.timer = setTimeout(() => {
      console.warn('No data received, connection may be dead');
      this.ws.close();
    }, this.timeout);
  }

  stop() {
    clearTimeout(this.timer);
  }
}

// Usage
const monitor = new ConnectionMonitor(ws, 60000);
monitor.start();

ws.onmessage = (event) => {
  monitor.onMessage();
  // Process message...
};

ws.onclose = () => {
  monitor.stop();
};

Application-Level Heartbeat

For additional reliability, implement your own heartbeat:

setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
  }
}, 30000);

// Server responds with { type: 'pong' }

Graceful Shutdown

When your client needs to disconnect cleanly:

function disconnect() {
  // Unsubscribe from all channels (optional but clean)
  for (const channel of subscriptions) {
    ws.send(JSON.stringify({
      type: 'unsubscribe',
      channel: channel
    }));
  }

  // Close with normal closure code
  ws.close(1000, 'Client disconnecting');
}

Shutdown Sequence

  1. Client sends close frame (code 1000)
  2. Server receives close frame
  3. Server sends close frame acknowledgment
  4. Both sides close TCP connection
  5. onclose fires with code 1000

State Recovery After Reconnect

After reconnecting, your client needs to restore its previous state. Note that authentication happens automatically via the api_key query parameter in your WebSocket URL - there is no separate authentication message.

ws.onopen = async () => {
  // Authentication is automatic via URL query param
  // Just resubscribe to all channels
  for (const channel of savedSubscriptions) {
    await subscribe(channel);
  }

  console.log('Subscriptions restored');
};

Important Considerations

  • Trades: You may miss trades during disconnect. If critical, use REST API to fetch missed trades.
  • Depth (Orderbook): On reconnect, clear your local orderbook state. The depth channel sends incremental updates (only changed levels) every ~250ms and full snapshots every ~60 seconds. Apply updates by setting levels (qty > 0) or removing them (qty = 0).
  • Candles: Request latest candle state; partial updates may have been missed.

Best Practices Summary

  1. Always implement reconnection with exponential backoff
  2. Maintain subscription list client-side for resubscription
  3. Handle all close codes appropriately
  4. Verify ping/pong is handled by your WebSocket library
  5. Implement timeout detection for network drops
  6. Request snapshots after reconnect for stateful data
  7. Add jitter to backoff to prevent thundering herd
  8. Log connection events for debugging

On this page