Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

BNB Smart Chain WebSocket RPC: WSS Endpoints, eth_subscribe, and Reconnection

Learn how to connect to BNB Smart Chain via WebSocket RPC, use eth_subscribe for real-time data, and handle reconnections with ethers.js and web3.js.

TL;DR

A comprehensive guide to using BNB Smart Chain WebSocket RPC: WSS endpoints, eth_subscribe methods, connection lifecycle, and reconnection strategies with code examples.

Direct Answer: How to Connect to BSC via WebSocket RPC

To connect to BNB Smart Chain (BSC) via WebSocket RPC, use a WSS endpoint such as wss://bsc-rpc.publicnode.com for the mainnet or wss://bsc-testnet.publicnode.com for the testnet. These endpoints support the standard Ethereum-compatible eth_subscribe methods, allowing you to receive real-time notifications for new blocks, pending transactions, and logs. For production applications, you should use a reliable provider like OnFinality's API service or a dedicated RPC provider, as public endpoints may have rate limits and connection instability.

This guide explains the BSC WebSocket RPC surface, how to subscribe and unsubscribe, and how to build a resilient connection with automatic reconnection and resubscription. We'll use ethers.js and web3.js for practical examples, and provide a troubleshooting checklist for common issues.

  • Mainnet WSS: wss://bsc-rpc.publicnode.com (public gateway, documented by PublicNode)
  • Testnet WSS: wss://bsc-testnet.publicnode.com (public gateway)
  • Provider-specific endpoints: vary by provider (e.g., QuickNode, Alchemy, OnFinality) – check your provider's documentation.

BSC WebSocket RPC Endpoints and eth_subscribe Methods

BNB Smart Chain is EVM-compatible, so its WebSocket RPC follows the Ethereum JSON-RPC specification. The primary method for real-time data is eth_subscribe, which creates a subscription and returns a subscription ID. The supported subscription types are: newHeads, logs, newPendingTransactions, and syncing.

The newHeads subscription sends a notification each time a new block is added to the chain. The logs subscription filters logs based on address and topics, and is ideal for tracking contract events. newPendingTransactions notifies you of transaction hashes entering the mempool, and syncing provides synchronization status changes.

The official BNB Chain documentation (docs.bnbchain.org) confirms that BSC supports these standard Ethereum subscriptions. For detailed JSON-RPC methods, refer to the BNB Chain JSON-RPC documentation.

  • newHeads: Get a notification for each new block header.
  • logs: Get logs that match a filter (address and topics).
  • newPendingTransactions: Get hashes of pending transactions.
  • syncing: Get notifications when syncing state changes.

Opening a WebSocket Connection and Subscribing with ethers.js

To connect to BSC via WebSocket using ethers.js, you create a WebSocketProvider instance with the WSS URL. Then you can use the on method to listen for events like block or logs. For example, to subscribe to new block headers, you can use provider.on('block', (blockNumber) => { ... }).

For logs, you can use provider.on('logs', filter, callback) where the filter is an object with address and topics. The callback receives a log object with fields like blockNumber, transactionHash, and data.

Here's a minimal example that connects to the BSC mainnet and logs new block numbers:

const { WebSocketProvider } = require('ethers');

const wsUrl = 'wss://bsc-rpc.publicnode.com';
const provider = new WebSocketProvider(wsUrl);

provider.on('block', (blockNumber) => {
  console.log('New block:', blockNumber);
});

// Keep the process alive
setTimeout(() => process.exit(0), 30000);

Subscribing with web3.js

With web3.js, you use web3.eth.subscribe to create a subscription. The method takes the subscription type and optional parameters. For example, to subscribe to new block headers, you can do:

const Web3 = require('web3');
const web3 = new Web3('wss://bsc-rpc.publicnode.com');

const subscription = web3.eth.subscribe('newBlockHeaders', (error, blockHeader) => {
  if (error) console.error(error);
  console.log('New block header:', blockHeader.number);
});

To unsubscribe, call subscription.unsubscribe() which returns a promise. For logs, you can pass a filter object as the second parameter: web3.eth.subscribe('logs', { address: '0x...', topics: [...] }, callback).

  • web3.eth.subscribe('newBlockHeaders') for new blocks.
  • web3.eth.subscribe('logs', filter) for logs.
  • web3.eth.subscribe('newPendingTransactions') for pending transactions.
  • web3.eth.subscribe('syncing') for sync status.

Understanding Notification Payloads

When you subscribe to newHeads, the notification payload is a block header object. It includes fields like number (block number), hash, parentHash, timestamp, and transactionsRoot. For logs, the payload is a log object with address, topics, data, blockNumber, transactionHash, and logIndex.

Here's an example of a newHeads notification payload:

{
  "jsonrpc": "2.0",
  "method": "eth_subscription",
  "params": {
    "subscription": "0x1234567890abcdef",
    "result": {
      "number": "0x1b4",
      "hash": "0x...",
      "parentHash": "0x...",
      "timestamp": "0x...",
      "transactionsRoot": "0x..."
    }
  }
}

For logs, the payload includes the log's address, topics, data, and block information. You can use these fields to trigger your application logic.

  • newHeads payload: block header with number, hash, timestamp, etc.
  • logs payload: log object with address, topics, data, blockNumber, etc.

Connection Lifecycle: Why BSC Public WSS Endpoints Drop Connections

Public WebSocket endpoints on BSC often drop connections due to rate limiting, idle timeouts, or server-side load balancing. For example, a public gateway might close connections that have been idle for a certain period or that exceed a request rate. This is documented behavior for many public RPC providers, but the exact limits vary by provider.

To maintain a stable connection, you need to implement a heartbeat (keepalive) mechanism. This can be done by sending a simple JSON-RPC request (like eth_blockNumber) periodically, or by using a ping/pong frame at the WebSocket protocol level. Many libraries, like ethers.js, have built-in keepalive options.

When a connection drops, you must reconnect and resubscribe to your subscriptions. This is because subscriptions are tied to the connection. A robust reconnection strategy involves detecting the disconnect, reconnecting, and then re-establishing all subscriptions. You should also handle duplicate notifications that may occur if the connection drops after a notification is sent but before you receive it.

  • Public endpoints may have idle timeouts (e.g., 60 seconds) and rate limits.
  • Use a heartbeat to keep the connection alive.
  • Reconnect and resubscribe on disconnect.
  • Handle duplicate notifications by using idempotent processing (e.g., checking block numbers).

Runnable Example: Reconnecting WebSocketProvider with Resubscribe

Below is a complete Node.js script using ethers.js that connects to BSC, subscribes to new blocks and logs, and automatically reconnects with resubscription. It includes a heartbeat and handles duplicate notifications by tracking the last block number.

The script uses WebSocketProvider and listens for the close event to trigger reconnection. It also sends a eth_blockNumber request every 15 seconds as a keepalive. When reconnecting, it re-subscribes to the same filters.

Expected output: The script logs new block numbers and any logs matching the filter. On disconnect, it logs a reconnection message and resumes.

const { WebSocketProvider } = require('ethers');

const WS_URL = 'wss://bsc-rpc.publicnode.com';
const LOG_FILTER = { address: '0x...' }; // Replace with your contract address

let provider;
let lastBlock = 0;
let reconnectAttempts = 0;

async function connect() {
  console.log('Connecting...');
  provider = new WebSocketProvider(WS_URL);

  provider.on('block', (blockNumber) => {
    if (blockNumber > lastBlock) {
      console.log('New block:', blockNumber);
      lastBlock = blockNumber;
    } else {
      console.log('Duplicate block:', blockNumber);
    }
  });

  provider.on('logs', (log) => {
    console.log('Log:', log.transactionHash, log.blockNumber);
  });

  provider.on('close', () => {
    console.log('Connection closed. Reconnecting...');
    reconnectAttempts++;
    setTimeout(connect, 1000 * Math.min(reconnectAttempts, 5));
  });

  provider.on('error', (error) => {
    console.error('WebSocket error:', error);
  });

  // Heartbeat: send a request every 15 seconds
  setInterval(async () => {
    try {
      await provider.send('eth_blockNumber', []);
    } catch (e) {
      console.error('Heartbeat failed:', e.message);
    }
  }, 15000);
}

connect();

// Keep process alive
setInterval(() => {}, 1000);

Troubleshooting Checklist: 4010, 429, and Connection Limits

When using BSC WebSocket RPC, you may encounter specific errors. Here are common issues and how to resolve them:

Error 4010 (Subscription limit exceeded): This occurs when you try to create too many subscriptions on a single connection. The limit is typically 10 subscriptions per connection, but it varies by provider. To fix, reduce the number of subscriptions or use multiple connections.

Error 429 (Too Many Requests): This is a rate limit error. Public endpoints often limit the number of requests per second. To avoid this, implement a rate limiter in your client or use a provider with higher limits.

Connection limit per IP: Some providers limit the number of concurrent connections from a single IP. If you hit this, you may need to use a provider that allows more connections or distribute your connections across multiple IPs.

Block height lag: If your subscription is not receiving the latest blocks, it may be due to the node being behind. Check the node's sync status using eth_syncing. If it's syncing, wait until it's fully synced.

  • 4010: Reduce subscriptions or use multiple connections.
  • 429: Implement rate limiting or upgrade your provider.
  • Connection limit: Use a provider with higher limits or distribute connections.
  • Block height lag: Check eth_syncing and wait for sync.

Tradeoffs and Limitations of BSC WebSocket RPC

WebSocket RPC is ideal for real-time applications, but it has tradeoffs. It requires a persistent connection, which can be resource-intensive. Public endpoints may be unreliable, so for production, consider using a dedicated provider like OnFinality's API service or a commercial provider.

Also, newPendingTransactions can be noisy and high-volume, so use it judiciously. For logs, you can reduce volume by specifying a narrow filter (address and topics).

Finally, note that BSC's block time is around 3 seconds, so newHeads notifications will arrive frequently. Ensure your client can handle the throughput.

  • Persistent connections require more resources.
  • Public endpoints may have rate limits and downtime.
  • Use filters to reduce log volume.
  • BSC block time is ~3 seconds, so expect frequent notifications.

Next Steps and Further Reading

Now that you understand BSC WebSocket RPC, you can build real-time applications. For more details on BSC network specifics, see the BNB Smart Chain network page. If you encounter disconnection issues, refer to our generic WebSocket RPC disconnection fixes.

For provider selection, check the BNB Chain RPC provider guidance and RPC pricing. You can also explore the OnFinality Learn hub for more tutorials.

Remember to test your implementation on the testnet first, and always monitor your connection health.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started