Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
RPC Troubleshooting12 min read

BNB Smart Chain RPC Rate Limits and 429s: What They Are and How to Handle Them

Learn why BNB Smart Chain RPC endpoints return 429 Too Many Requests, how rate limits work, and practical fixes including retry with backoff, eth_getLogs optimization, WebSocket subscriptions, and moving to a dedicated endpoint.

TL;DR

BNB Smart Chain RPC endpoints enforce rate limits to protect infrastructure. When you exceed them, you get HTTP 429 Too Many Requests. This article explains the causes, shows how to test with curl, and provides a checklist to fix 429s, including retry with backoff, optimizing eth_getLogs, using WebSockets, and upgrading to a dedicated endpoint.

What Are BNB Smart Chain RPC Rate Limits?

BNB Smart Chain (BSC) RPC endpoints, whether public or commercial, enforce rate limits to prevent any single client from monopolizing resources. When you exceed the allowed number of requests per second or per minute, the server responds with HTTP status code 429 Too Many Requests. This is a standard mechanism to ensure fair usage and protect the node from overload.

Public endpoints, such as the ones listed in the BNB Smart Chain JSON-RPC endpoint documentation, are free but have strict limits. These limits are often not explicitly documented, but they are typically lower than those of dedicated providers. If you are building a production application, relying on public endpoints is risky because you can hit 429s during traffic spikes or when performing heavy queries.

The exact limits vary by provider. For example, OnFinality's BNB Smart Chain network page offers both public and dedicated endpoints with different rate limits. Public endpoints are shared, while dedicated endpoints provide higher throughput and more predictable performance. Understanding these limits is the first step to avoiding 429s.

  • Rate limits are typically expressed as requests per second (RPS) or requests per minute (RPM).
  • 429 responses include a Retry-After header in some cases, but not always.
  • Public endpoints are shared across all users, so limits are lower and more variable.

Why Do 429 Errors Happen? Common Causes

429 errors on BSC RPC endpoints are not random. They happen because your client is sending too many requests in a short time, or because individual requests are too expensive. Here are the most common causes:

Bursting: Sending a burst of requests in a short period, for example, when your application starts up and fetches data for many addresses at once. Even if your average request rate is low, a burst can exceed the limit.

Expensive methods: Some JSON-RPC methods are computationally heavy. For instance, eth_getLogs over a wide block range or for a popular contract can scan thousands of blocks and return massive amounts of data. Each such request can count as multiple 'units' against your rate limit, or simply take so long that the server times out or returns 429.

Archive queries: Accessing historical state (e.g., eth_call at an old block) requires archive nodes, which are more resource-intensive. If your endpoint does not support archive data, you might get errors, but if it does, these queries are more expensive.

Lack of caching: Repeating the same request multiple times without caching the result wastes your quota. For example, fetching the same block or transaction repeatedly.

WebSocket misuse: Using WebSocket subscriptions incorrectly, such as subscribing to too many events or not unsubscribing when done, can also lead to rate limiting.

  • Bursting is the most common cause in production apps.
  • eth_getLogs is a known heavy method; always optimize it.
  • Archive queries are more expensive than regular queries.
  • Caching can dramatically reduce your request count.

How to Test: Comparing a Cheap vs Expensive Request with curl

To understand the impact of different methods, you can run a simple curl test against a BSC RPC endpoint. We'll compare a cheap method (eth_blockNumber) with an expensive one (eth_getLogs over a wide block range). This will show you the difference in response time and payload size, which correlates with server load.

First, set your endpoint. For this example, we'll use the public BSC endpoint https://bsc-dataseed.binance.org/ (you can replace it with your own endpoint). Run the following commands in your terminal:

Cheap request: eth_blockNumber

curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Expensive request: eth_getLogs

curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1000000","toBlock":"0x1000100","address":"0x55d398326f99059ff775485246999027b3197955"}],"id":1}'

The second request asks for logs from a 256-block range for the USDT contract. This will scan many blocks and return a large response. You can measure the time with curl -w "%{time_total}" and the size with -o /dev/null -s -w "%{size_download}". On a public endpoint, you might see a 429 if you run the expensive request repeatedly.

Expected results: The cheap request should return quickly (under a second) with a small JSON payload. The expensive request will take longer (several seconds) and return a much larger payload. If you run the expensive request multiple times in a row, you may start getting 429 responses, demonstrating how heavy methods can exhaust your quota.

  • Use curl -w to measure time and size.
  • Run the expensive request multiple times to trigger a 429.
  • Always test against your actual endpoint to see its limits.
curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1000000","toBlock":"0x1000100","address":"0x55d398326f99059ff775485246999027b3197955"}],"id":1}'

# To measure time and size:
curl -w "Time: %{time_total}s, Size: %{size_download} bytes" -o /dev/null -s -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1000000","toBlock":"0x1000100","address":"0x55d398326f99059ff775485246999027b3197955"}],"id":1}'

How to Handle 429s: Retry with Backoff

The simplest way to handle 429s is to implement retry logic with exponential backoff. When you receive a 429, wait a short time and try again, increasing the wait time with each retry. This gives the server time to recover and reduces the chance of overwhelming it further.

Here's a Python example using the requests library:

import requests
import time

url = "https://bsc-dataseed.binance.org/"
headers = {"Content-Type": "application/json"}
payload = {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}

max_retries = 5
for attempt in range(max_retries):
    response = requests.post(url, json=payload, headers=headers)
    if response.status_code == 200:
        print(response.json())
        break
    elif response.status_code == 429:
        wait = 2 ** attempt  # exponential backoff
        print(f"Rate limited. Waiting {wait} seconds...")
        time.sleep(wait)
    else:
        print(f"Error: {response.status_code}")
        break

This code retries up to 5 times, waiting 1, 2, 4, 8, and 16 seconds between attempts. You can adjust the base and maximum wait times based on your needs. Also, check the Retry-After header if present, as it tells you exactly how long to wait.

For production, consider using a library like tenacity or backoff to handle retries more robustly. Remember to also handle other errors like 5xx and network timeouts.

  • Exponential backoff is a standard pattern for rate limiting.
  • Respect the Retry-After header if provided.
  • Add jitter to avoid thundering herd effects.
import requests
import time

url = "https://bsc-dataseed.binance.org/"
headers = {"Content-Type": "application/json"}
payload = {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}

max_retries = 5
for attempt in range(max_retries):
    response = requests.post(url, json=payload, headers=headers)
    if response.status_code == 200:
        print(response.json())
        break
    elif response.status_code == 429:
        wait = 2 ** attempt  # exponential backoff
        print(f"Rate limited. Waiting {wait} seconds...")
        time.sleep(wait)
    else:
        print(f"Error: {response.status_code}")
        break

Optimizing eth_getLogs to Reduce Load

eth_getLogs is one of the most expensive methods on BSC. It can scan a large number of blocks and return huge amounts of data. To reduce the chance of hitting rate limits, you should optimize your queries:

Narrow the block range: Instead of querying a wide range, split it into smaller chunks. For example, query 100 blocks at a time instead of 1000. This reduces the server load and the response size.

Use specific addresses and topics: Filter by contract address and event topics to reduce the number of logs returned. The more specific your filter, the less data the server has to process.

Use pagination: If you need logs from a large range, use fromBlock and toBlock to paginate. For example, first query blocks 1-100, then 101-200, and so on.

Consider using WebSocket subscriptions: For real-time event monitoring, use eth_subscribe to get logs as they are emitted, rather than polling with eth_getLogs. This is more efficient and reduces the number of requests.

Here's an example of a more optimized eth_getLogs call:

curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1000000","toBlock":"0x1000064","address":"0x55d398326f99059ff775485246999027b3197955","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}],"id":1}'

This query only asks for 100 blocks and filters for the Transfer event (topic 0xddf252...). It will return a much smaller response and put less load on the server.

  • Always narrow the block range to the minimum needed.
  • Use address and topic filters to reduce data.
  • Pagination is your friend for large ranges.
  • WebSocket subscriptions are better for real-time data.
curl -X POST https://bsc-dataseed.binance.org/ -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1000000","toBlock":"0x1000064","address":"0x55d398326f99059ff775485246999027b3197955","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}],"id":1}'

Using WebSocket Subscriptions to Avoid Polling

If your application needs real-time data, such as new blocks or event logs, using WebSocket subscriptions is far more efficient than polling with HTTP. Instead of sending repeated eth_getLogs or eth_blockNumber requests, you subscribe once and receive updates as they happen. This drastically reduces the number of requests and helps you stay under rate limits.

Here's a simple Node.js example using the ws library to subscribe to new block headers:

const WebSocket = require('ws');

const ws = new WebSocket('wss://bsc-ws-node.nariber.org'); // Replace with your WebSocket endpoint

ws.on('open', () => {
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'eth_subscribe',
    params: ['newHeads'],
    id: 1
  }));
});

ws.on('message', (data) => {
  console.log(data.toString());
});

ws.on('error', (err) => {
  console.error(err);
});

You can also subscribe to logs using eth_subscribe with the logs parameter. This is ideal for monitoring contract events without polling.

Note that WebSocket connections also have limits, such as the number of active subscriptions per connection. Be sure to manage your subscriptions and close them when no longer needed.

For a production-grade WebSocket endpoint, consider using a provider like OnFinality's BNB Smart Chain RPC Assistant, which offers reliable WebSocket support.

  • WebSocket subscriptions reduce request count significantly.
  • Use eth_subscribe for newHeads, logs, and other events.
  • Manage subscriptions to avoid hitting connection limits.
const WebSocket = require('ws');

const ws = new WebSocket('wss://bsc-ws-node.nariber.org'); // Replace with your WebSocket endpoint

ws.on('open', () => {
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'eth_subscribe',
    params: ['newHeads'],
    id: 1
  }));
});

ws.on('message', (data) => {
  console.log(data.toString());
});

ws.on('error', (err) => {
  console.error(err);
});

Moving to a Dedicated BSC Endpoint

If you are building a serious application, relying on public endpoints is not sustainable. Public endpoints are shared, have low rate limits, and can be unreliable. The best long-term solution is to use a dedicated BSC endpoint from a provider like OnFinality.

OnFinality offers dedicated BSC endpoints with higher rate limits, dedicated resources, and 24/7 support. You can also use the API service to manage your endpoints and monitor usage. With a dedicated endpoint, you get a private URL that is not shared with other users, so you are less likely to hit rate limits.

Dedicated endpoints also support archive data, which is essential for certain queries. They provide WebSocket connections for real-time applications. The pricing page shows the different tiers available, so you can choose one that fits your needs.

When you move to a dedicated endpoint, you should still implement the best practices mentioned above, such as caching and optimizing queries, to make the most of your quota. But you will have much more headroom to work with.

If you are not ready to upgrade, you can also use OnFinality's RPC Assistant to find the best endpoint for your use case, including public and dedicated options.

  • Dedicated endpoints provide higher rate limits and reliability.
  • OnFinality offers dedicated BSC endpoints with archive support.
  • Use the API service to monitor and manage your endpoints.

Fix Checklist: Resolving BSC RPC 429 Errors

Here is a practical checklist to resolve 429 errors on BSC RPC endpoints. Go through these steps in order:

  1. Identify the cause: Use curl to test your requests and see which methods are heavy. Monitor your request rate and payload sizes.

  1. Implement retry with backoff: Add exponential backoff to your client to handle transient 429s gracefully.

  1. Optimize eth_getLogs: Narrow block ranges, use filters, and paginate large queries.

  1. Use WebSocket subscriptions: For real-time data, subscribe instead of polling.

  1. Cache responses: Cache frequently requested data (e.g., block numbers, token prices) to reduce duplicate requests.

  1. Upgrade to a dedicated endpoint: If you still hit limits, move to a dedicated BSC endpoint from a provider like OnFinality.

  1. Monitor usage: Use tools like the OnFinality API service to track your request volume and adjust accordingly.

  1. Consider load balancing: If you have multiple endpoints, distribute requests across them to reduce load on any single one.

For more detailed troubleshooting, see our generic RPC 429 troubleshooting guide.

  • Always start with the cheapest fix: retry and optimize.
  • Dedicated endpoints are the most reliable solution.
  • Monitoring is key to preventing future issues.

Tradeoffs and Limitations

While the solutions above are effective, they come with tradeoffs. Retry with backoff adds latency to your application, especially during high load. Optimizing eth_getLogs may require more complex code and can miss events if you paginate incorrectly. WebSocket subscriptions require maintaining persistent connections, which can be more complex to manage in serverless environments.

Dedicated endpoints cost money, but they offer the best performance and reliability. For small projects, public endpoints with careful optimization may be sufficient, but for production, the investment is worth it.

Also, note that rate limits are not always documented. The exact numbers for public endpoints are often not published, so you need to test and observe. OnFinality's pricing page provides clear details for dedicated endpoints, so you know what to expect.

Finally, remember that rate limits are there to protect the network. By following best practices, you not only avoid 429s but also contribute to the overall health of the BSC ecosystem.

  • Retry adds latency; optimize to minimize it.
  • WebSockets are not ideal for all architectures.
  • Dedicated endpoints are a paid solution but offer the best experience.

Next Steps

Now that you understand BSC RPC rate limits and how to handle 429s, you can improve your application's reliability. Start by implementing the checklist above, and consider moving to a dedicated endpoint for production.

Explore OnFinality's BNB Smart Chain network page to see available endpoints and pricing. You can also use the RPC Assistant to find the best endpoint for your needs. For more general RPC troubleshooting, check out our learn hub and the generic 429 guide.

If you have any questions, our team is happy to help. Visit our API service to get started with a dedicated endpoint today.

  • Implement the checklist in your codebase.
  • Evaluate dedicated endpoints for production.
  • Stay updated with OnFinality's resources.

Never Worry about Infrastructure Again

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

Get Started