Best Practices

Automatic Reconnection

WebSocket connections will eventually disconnect -- due to network issues, server deployments, or the 1-hour maximum connection lifetime. Your client must implement automatic reconnection.

Reconnection Strategy

Use exponential backoff with jitter to avoid all your clients reconnecting at the same instant:

class InsightXWebSocket {
  constructor(apiKey) {
    this.url = `wss://api.insightx.network/ws/?api-key=${apiKey}`;
    this.subscriptions = [];      // track subscriptions for re-subscribe
    this.backoff = 500;           // initial backoff in ms
    this.maxBackoff = 30000;      // max backoff: 30s
    this.connect();
  }

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

    this.ws.onopen = () => {
      this.backoff = 500; // reset backoff on successful connect
    };

    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);

      if (msg.op === "ready") {
        // Re-subscribe to previously active topics
        this.resubscribe();
      }

      if (msg.op === "event") {
        this.handleEvent(msg);
      }
    };

    this.ws.onclose = (event) => {
      if (event.code === 4401) {
        console.error("Authentication failed. Not reconnecting.");
        return;
      }

      // Reconnect with exponential backoff + jitter
      const jitter = Math.random() * this.backoff * 0.5;
      const delay = this.backoff + jitter;
      console.log(`Reconnecting in ${Math.round(delay)}ms...`);
      setTimeout(() => this.connect(), delay);
      this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
    };
  }

  subscribe(network, address, stream) {
    this.subscriptions.push({ network, address, stream });
    this.ws.send(JSON.stringify({
      op: "subscribe",
      network, address, stream
    }));
  }

  resubscribe() {
    if (this.subscriptions.length === 0) return;
    this.ws.send(JSON.stringify({
      op: "subscribe",
      mode: "replace",
      subs: this.subscriptions
    }));
  }

  handleEvent(msg) {
    // Your event handling logic here
    console.log(`[${msg.stream}] ${msg.network}:${msg.address}`, msg.data);
  }
}

const client = new InsightXWebSocket("your-api-key-here");
client.subscribe("sol", "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "clusters");

Python Example

import asyncio
import json
import websockets

API_KEY = "your-api-key-here"
URL = f"wss://api.insightx.network/ws/?api-key={API_KEY}"

SUBSCRIPTIONS = [
    {"network": "sol", "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "stream": "clusters"},
]

async def connect():
    backoff = 0.5
    max_backoff = 30.0

    while True:
        try:
            async with websockets.connect(URL) as ws:
                backoff = 0.5  # reset on successful connect

                # Wait for ready
                ready = json.loads(await ws.recv())
                assert ready["op"] == "ready"

                # Re-subscribe
                await ws.send(json.dumps({
                    "op": "subscribe",
                    "mode": "replace",
                    "subs": SUBSCRIPTIONS,
                }))

                # Process events
                async for raw in ws:
                    msg = json.loads(raw)
                    if msg["op"] == "event":
                        print(f"[{msg['stream']}] {msg['network']}:{msg['address']}")

        except websockets.ConnectionClosedError as e:
            if e.code == 4401:
                print("Authentication failed. Check your API key.")
                return
            print(f"Disconnected ({e.code}). Reconnecting in {backoff:.1f}s...")

        except Exception as e:
            print(f"Error: {e}. Reconnecting in {backoff:.1f}s...")

        await asyncio.sleep(backoff)
        backoff = min(backoff * 2, max_backoff)

asyncio.run(connect())

Keep-Alive Pings

Send a ping message every 30 seconds to keep the connection alive and detect stale connections early.

setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ op: "ping", id: Date.now().toString() }));
  }
}, 30000);

Use Bulk Subscriptions

When subscribing to multiple addresses, use the subs array instead of sending individual messages. This reduces round-trips and is processed more efficiently.

{
  "op": "subscribe",
  "subs": [
    { "network": "sol", "address": "addr1...", "stream": "clusters" },
    { "network": "sol", "address": "addr2...", "stream": "clusters" },
    { "network": "sol", "address": "addr3...", "stream": "clusters" }
  ]
}

Track Subscriptions Client-Side

Maintain a local list of your active subscriptions so you can re-subscribe after reconnecting. Use mode: "replace" on reconnect to ensure a clean state without duplicates.

Handle the Connection Lifetime

Connections have a maximum lifetime of 1 hour. After this, the server closes the connection. Your reconnection logic handles this automatically, but be aware that:

  • You will receive a close frame when the lifetime is reached.
  • The reconnect should be immediate (no backoff needed).
  • All data delivered before the close is reliable.
  • Events that occur during the brief reconnect window (typically under 1 second) may be missed.

Use Full-State Replacement

Each cluster event contains the complete, current cluster state for a token -- not a diff or delta. You can safely replace your local state with the contents of each new event without needing to merge or reconcile.

Don't Rely on Cross-Token Ordering

Events for different tokens may arrive in any order. Use the timestamp field in each event if you need to sequence updates.

Monitor Your Topic Count

The topics_now field in subscribe/unsubscribe responses tells you how many topics are active. Monitor this to stay within your plan limits and avoid TOPIC_LIMIT errors.