WebSocket vs HTTP Polling for Blockchain Events
You need to react to on-chain events — a new block, a contract log, an incoming payment. Two ways to get them over RPC: poll (ask the node "anything new?" on a timer) or subscribe (open a WebSocket and let the node push events to you). Both work. They fail differently, scale differently, and the "right" answer depends on what you're building. Here's the honest comparison.
The two models
HTTP polling is request/response on a loop. You call eth_blockNumber or eth_getLogs every few seconds and diff against what you saw last time. The node is passive; your client drives everything.
// Poll for new logs every 3 seconds
let fromBlock = await client.getBlockNumber();
setInterval(async () => {
const latest = await client.getBlockNumber();
if (latest > fromBlock) {
const logs = await client.getLogs({ address, fromBlock: fromBlock + 1n, toBlock: latest });
logs.forEach(handleLog);
fromBlock = latest;
}
}, 3000);
WebSocket subscription is a persistent connection the node pushes to. You eth_subscribe once, then events arrive as they happen (see Subscribing with eth_subscribe).
const ws = new WebSocket("wss://rpc.swiftnodes.io/ws/eth?key=YOUR_API_KEY");
ws.onopen = () => ws.send(JSON.stringify({
jsonrpc: "2.0", id: 1, method: "eth_subscribe", params: ["logs", { address }],
}));
ws.onmessage = (e) => handleLog(JSON.parse(e.data));
Head to head
| HTTP polling | WebSocket subscription | |
|---|---|---|
| Latency | Up to your poll interval (1–12s typical) | Near-instant (pushed on event) |
| Overhead at rest | A request every interval, even when nothing happens | One idle connection, no traffic until an event |
| Complexity | Simple — stateless requests, easy retries | Stateful — connection lifecycle, reconnection, resubscribe |
| Robustness | Very — each poll is independent; a failure just retries next tick | Fragile — a dropped connection silently stops events |
| Missed events | None if you track fromBlock correctly |
Yes — anything during a disconnect is lost |
| Scaling cost | More requests as you add watchers/intervals | One connection carries many subscriptions cheaply |
| Historical data | Native — just set fromBlock in the past |
Not possible — subscriptions are live-only |
Where polling wins
- Reliability-critical backends. Each poll is independent and idempotent. If one request times out, the next tick retries — no connection state to corrupt, no silent stall. For "must not miss a payment" systems, this simplicity is a feature.
- Backfilling and historical queries. Subscriptions are live-only — they can't tell you about the past. Any indexer that starts from a historical block must poll
eth_getLogsover ranges to catch up (minding range caps and reorgs). - Serverless / stateless environments. A Lambda can't hold a WebSocket open between invocations. A cron-triggered poll fits the model perfectly.
- Low-frequency needs. If checking every 12 seconds is fine, a one-line polling loop beats the machinery of a resilient WebSocket client.
Where WebSocket wins
- Latency-sensitive apps. Trading bots, liquidation watchers, real-time dashboards — you want the event the instant it lands, not up to a poll interval later. On a fast chain, polling adds meaningful lag.
- Many watchers, low traffic. One WebSocket can carry dozens of subscriptions and stays silent until something fires. Polling the same set means N requests every interval whether or not anything changed — wasteful on your rate limit and the node.
- Live UX. New-block ticks, live mempool feeds (pending transactions), streaming order books — anything a user watches update in real time.
The catch with WebSocket: reconnection
The one failure mode that bites everyone: WebSocket connections drop — networks blip, load balancers recycle idle connections, nodes restart. When that happens, your subscription silently stops and any events during the gap are lost — no error, no gap marker, just missing data. A production WebSocket client must:
- Detect the drop (heartbeat/ping-pong —
onclosedoesn't always fire promptly). - Reconnect with backoff and re-subscribe (subscriptions don't survive reconnection).
- Backfill the gap — poll
eth_getLogsfrom the last block you saw to now, so nothing is missed.
That third step is the tell: a correct WebSocket setup already contains a polling fallback. We covered the full pattern in Handling WebSocket Reconnections Without Losing Events — it's the difference between a demo and something you can trust with money.
Why production systems use both
The two aren't rivals so much as layers. The robust pattern most serious indexers land on:
- WebSocket for the live edge — instant reaction to new blocks/logs as they arrive.
- Polling for correctness — a periodic
eth_getLogssweep (and a backfill on every reconnect) that guarantees you eventually see every event, even the ones the socket missed.
The WebSocket gives you speed; the poll gives you a guarantee. If you can only build one first, build the poll — it's the one that's correct on its own. Add WebSocket when latency matters. And remember the model shifts by chain: on a single-sequencer L2 there's no gossiped pending pool to subscribe to, and on a fast chain your poll interval has to tighten to keep up.
The short version
Polling is simple, robust, stateless, and the only option for historical/backfill data — at the cost of latency and wasted requests when idle. WebSocket is near-instant and cheap at rest for many watchers — at the cost of connection management and silently-dropped events on reconnect. Latency-critical and live-UX work leans WebSocket; reliability-critical, serverless, and backfill work leans polling. The strongest systems use WebSocket for speed and polling for the guarantee — and every correct WebSocket client has a polling backfill hiding inside it.
Both models need an endpoint that serves HTTP and WebSocket equally well. A flat-rate Ethereum RPC endpoint gives you eth_getLogs polling and eth_subscribe streaming across dozens of chains under one key. Grab a free key and point your app at:
https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY # HTTP
wss://rpc.swiftnodes.io/ws/eth?key=YOUR_API_KEY # WebSocket
Related posts
- Subscribing to Contract Events with eth_subscribe (logs)
Polling eth_getLogs in a loop is the slow, brittle way to watch contract events. eth_subscribe pushes logs to you over WebSocket the instant they're mined. Here's how to build a subscription that filters correctly, survives reconnects, and never silently misses an event.
- How to Handle WebSocket Reconnections Without Losing Events
A WebSocket subscription that silently drops is worse than no subscription at all — you keep running, but events vanish into the gap. Here's how to build reconnect logic that detects the drop, backs off, re-subscribes, and backfills the missed events so your indexer never loses a log.
- What Is MEV? A Developer's Plain Explainer
MEV — maximal extractable value — is why your swap sometimes executes at a worse price than you saw, and why validators reorder transactions for profit. A plain-English tour of front-running, sandwiches, and back-running, how the public mempool enables it, and what you can actually do about it.