WebSockets Quickstart

Subscribe to real-time blockchain events over a persistent WebSocket connection. Receive new blocks, logs, and pending transactions the moment they appear.

Available Subscriptions

SubscriptionDescription
newHeadsNew block headers as they are mined
logsSmart contract event logs with optional topic/address filters
newPendingTransactionsTransaction hashes as they enter the mempool
syncingNode sync status changes
1

Connect to the WebSocket Endpoint

The WebSocket URL follows the same chain/network pattern as the REST API. Pass your API key as a query parameter.

wss://api.bootnode.dev/v1/ws/{chain}/{network}?apiKey=YOUR_API_KEY

// Examples:
// wss://api.bootnode.dev/v1/ws/ethereum/mainnet?apiKey=bn_live_...
// wss://api.bootnode.dev/v1/ws/base/mainnet?apiKey=bn_live_...
// wss://api.bootnode.dev/v1/ws/polygon/mainnet?apiKey=bn_live_...
// Node.js / Browser
const ws = new WebSocket(
  "wss://api.bootnode.dev/v1/ws/ethereum/mainnet?apiKey=" +
  process.env.BOOTNODE_API_KEY
);

ws.onopen = () => {
  console.log("Connected to Bootnode WebSocket");
};

ws.onerror = (error) => {
  console.error("WebSocket error:", error);
};

ws.onclose = (event) => {
  console.log(`WebSocket closed: code=${event.code} reason=${event.reason}`);
};
2

Subscribe to New Block Headers

The newHeads subscription emits a message every time a new block is mined. Each message contains the block header with number, hash, timestamp, gas used, and base fee.

const ws = new WebSocket(
  "wss://api.bootnode.dev/v1/ws/ethereum/mainnet?apiKey=" +
  process.env.BOOTNODE_API_KEY
);

ws.onopen = () => {
  // Subscribe to new block headers
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "eth_subscribe",
    params: ["newHeads"],
  }));
};

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

  // Subscription confirmation
  if (message.id === 1) {
    console.log("Subscribed! ID:", message.result);
    // => "Subscribed! ID: 0x1a2b3c4d"
    return;
  }

  // New block header
  if (message.method === "eth_subscription") {
    const block = message.params.result;
    console.log("New block:", {
      number: parseInt(block.number, 16),
      hash: block.hash,
      timestamp: new Date(parseInt(block.timestamp, 16) * 1000),
      gasUsed: parseInt(block.gasUsed, 16),
      gasLimit: parseInt(block.gasLimit, 16),
      baseFeePerGas: parseInt(block.baseFeePerGas, 16) / 1e9 + " gwei",
      transactionCount: block.transactions?.length ?? "N/A",
    });
  }
};

// Example output:
// New block: {
//   number: 20574335,
//   hash: "0xabc123...",
//   timestamp: 2026-01-15T10:35:22.000Z,
//   gasUsed: 15000000,
//   gasLimit: 30000000,
//   baseFeePerGas: "12.5 gwei",
//   transactionCount: 142
// }
3

Subscribe to Logs with Filters

Subscribe to smart contract events by specifying contract addresses and/or event topic signatures. This example monitors USDC Transfer events.

const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
// keccak256("Transfer(address,address,uint256)")
const TRANSFER_TOPIC =
  "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

ws.onopen = () => {
  // Subscribe to USDC Transfer events
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 2,
    method: "eth_subscribe",
    params: [
      "logs",
      {
        address: USDC_ADDRESS,
        topics: [TRANSFER_TOPIC],
      },
    ],
  }));
};

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

  if (message.id === 2) {
    console.log("Subscribed to USDC transfers! ID:", message.result);
    return;
  }

  if (message.method === "eth_subscription") {
    const log = message.params.result;

    // Decode Transfer event
    const from = "0x" + log.topics[1].slice(26);
    const to = "0x" + log.topics[2].slice(26);
    const amount = BigInt(log.data);
    const usdc = Number(amount) / 1e6; // USDC has 6 decimals

    console.log(`USDC Transfer: ${from} -> ${to}: $${usdc.toFixed(2)}`);
    console.log(`  Block: ${parseInt(log.blockNumber, 16)}`);
    console.log(`  Tx: ${log.transactionHash}`);
  }
};

// Example output:
// USDC Transfer: 0xd8dA...6045 -> 0x9876...5432: $1500.00
//   Block: 20574335
//   Tx: 0xabc123...
4

Subscribe to Pending Transactions

Monitor the mempool for new pending transactions. Useful for MEV detection, frontrunning protection, or real-time transaction feeds.

ws.onopen = () => {
  // Subscribe to pending transactions
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 3,
    method: "eth_subscribe",
    params: ["newPendingTransactions"],
  }));
};

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

  if (message.id === 3) {
    console.log("Subscribed to pending txs! ID:", message.result);
    return;
  }

  if (message.method === "eth_subscription") {
    const txHash = message.params.result;
    console.log("Pending tx:", txHash);
    // => "Pending tx: 0xabc123def456..."

    // Optionally fetch full transaction details via RPC
    // ws.send(JSON.stringify({
    //   jsonrpc: "2.0",
    //   id: Date.now(),
    //   method: "eth_getTransactionByHash",
    //   params: [txHash],
    // }));
  }
};
5

Handle Reconnection

WebSocket connections can drop due to network issues or server maintenance. Always implement reconnection logic with exponential backoff.

class BootnodeWebSocket {
  private ws: WebSocket | null = null;
  private url: string;
  private subscriptions: Array<{ id: number; params: any[] }> = [];
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private baseDelay = 1000;
  private onMessage: (data: any) => void;

  constructor(
    chain: string,
    network: string,
    apiKey: string,
    onMessage: (data: any) => void
  ) {
    this.url =
      `wss://api.bootnode.dev/v1/ws/${chain}/${network}?apiKey=${apiKey}`;
    this.onMessage = onMessage;
    this.connect();
  }

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

    this.ws.onopen = () => {
      console.log("WebSocket connected");
      this.reconnectAttempts = 0;

      // Re-subscribe to all previous subscriptions
      for (const sub of this.subscriptions) {
        this.ws!.send(JSON.stringify({
          jsonrpc: "2.0",
          id: sub.id,
          method: "eth_subscribe",
          params: sub.params,
        }));
      }
    };

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

    this.ws.onclose = (event) => {
      console.log(`WebSocket closed: ${event.code} ${event.reason}`);
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error("WebSocket error:", error);
      // onclose will fire after onerror, triggering reconnect
    };
  }

  private reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error("Max reconnection attempts reached. Giving up.");
      return;
    }

    const delay = this.baseDelay * Math.pow(2, this.reconnectAttempts);
    const jitter = Math.random() * 1000;
    this.reconnectAttempts++;

    console.log(
      `Reconnecting in ${delay + jitter}ms ` +
      `(attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
    );

    setTimeout(() => this.connect(), delay + jitter);
  }

  subscribe(id: number, params: any[]) {
    this.subscriptions.push({ id, params });

    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        jsonrpc: "2.0",
        id,
        method: "eth_subscribe",
        params,
      }));
    }
  }

  unsubscribe(subscriptionId: string) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        jsonrpc: "2.0",
        id: Date.now(),
        method: "eth_unsubscribe",
        params: [subscriptionId],
      }));
    }
  }

  close() {
    this.maxReconnectAttempts = 0; // Prevent reconnection
    this.ws?.close();
  }
}

// Usage:
const client = new BootnodeWebSocket(
  "ethereum",
  "mainnet",
  process.env.BOOTNODE_API_KEY!,
  (data) => {
    if (data.method === "eth_subscription") {
      console.log("Event:", data.params.result);
    }
  }
);

// Subscribe to new blocks
client.subscribe(1, ["newHeads"]);

// Subscribe to USDC transfers
client.subscribe(2, ["logs", {
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
}]);

// Clean shutdown
process.on("SIGINT", () => {
  client.close();
  process.exit(0);
});

Unsubscribing

To stop receiving events for a subscription, send an eth_unsubscribe request with the subscription ID returned from the initial subscribe call.

// Unsubscribe
ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 99,
  method: "eth_unsubscribe",
  params: ["0x1a2b3c4d"], // subscription ID from eth_subscribe response
}));

// Response:
// { "jsonrpc": "2.0", "id": 99, "result": true }

Connection Limits

PlanConcurrent ConnectionsSubscriptions per Connection
Free25
Growth1050
EnterpriseCustomCustom

Next Steps