Real‑Time Event Listening with Web3.js eth.subscribe
Learn how to listen to smart contract events in real time using Web3.js eth.subscribe, handle reconnections, and weigh the trade‑offs of persistent WebSocket connections.
25 Jul 2026, 20:46 UTC

Problem: Missing Real‑Time Updates in a dApp
When building a decentralized application you often need to react instantly to state changes—for example, updating a UI when a token transfer occurs. Relying on periodic polling introduces latency and wastes node resources, while missing an event can break the user experience. Web3.js offers a persistent subscription API that pushes new log entries to your client as soon as they appear on the blockchain.
Setting Up a Raw Log Subscription
const Web3 = require('web3');
const wsProvider = new Web3.providers.Websocket('wss://mainnet.infura.io/ws/v3/');
const web3 = new Web3(wsProvider);
const contractAddress = '0x';
const eventSignature = web3.utils.sha3('Transfer(address,address,uint256)');
const subscription = web3.eth.subscribe('logs', {
address: contractAddress,
topics: [eventSignature]
}, (error, result) => {
if (error) console.error('Subscription error:', error);
else console.log('New log:', result);
});
// To stop later: subscription.unsubscribe();
Run this script with Node.js after installing the web3 package (npm install web3). Replace <YOUR_INFURA_PROJECT_ID> with a valid WebSocket project ID and <CONTRACT_ADDRESS> with the address of the contract you wish to monitor. The callback receives a log object each time a matching event is emitted; you can inspect result.data or result.topics to decode the payload. If the underlying WebSocket drops, the callback will not fire until the connection is restored.
Using the Contract‑Event Helper
For a more ergonomic approach, Web3.js lets you bind directly to a contract’s ABI‑defined event:
const contract = new web3.eth.Contract(abi, contractAddress);
const event = contract.events.Transfer({}, (error, event) => {
if (error) console.error('Event error:', error);
else console.log('Transfer:', event.returnValues);
});
// The returned object is an EventEmitter; you can also use .on('data', handler) or .once.
Internally this creates the same log subscription, but the library filters topics and decodes the data for you. Choose this pattern when you already have the contract ABI and want typed return values.
Handling Disconnections and Reconnection
A subscription lives as long as the WebSocket stays open. Public endpoints may close idle connections or enforce rate limits, so you should listen for provider‑level errors and attempt to resubscribe:
wsProvider.on('error', err => console.error('WS error:', err));
wsProvider.on('end', () => {
console.log('WebSocket closed – attempting reconnect...');
// Simple reconnection: recreate the provider and resubscribe
const newProvider = new Web3.providers.Websocket('wss://mainnet.infura.io/ws/v3/');
const newWeb3 = new Web3(newProvider);
// repeat subscription setup with newWeb3
});
To avoid losing events during the brief downtime, persist the highest block number you have processed (e.g., in a local file or database) and, upon reconnection, request logs from that block forward using web3.eth.getPastLogs as a fallback.
Trade‑offs and Limitations
- Each open subscription holds a WebSocket connection, consuming memory and bandwidth on the node.
- Free or shared Infura/Alchemy endpoints may drop connections under heavy load; consider a dedicated node or a self‑hosted WS endpoint for high‑frequency contracts.
- If your reconnection logic fails or you miss the window, events emitted while offline can be lost unless you implement a block‑number checkpoint and a catch‑up query.
- The subscription API works only over WebSocket; HTTP providers cannot push events in real time.
Actionable Closing
- Install the library:
npm install web3@^4.0.0(adjust the version to match your project). - Obtain a WebSocket endpoint—Infura, Alchemy, or a self‑hosted Geth/OpenEthereum node.
- Decide between raw log subscription (
web3.eth.subscribe('logs', ...)) for maximum flexibility or the contract‑event helper (contract.events.EventName) for automatic decoding. - Wrap the subscription setup in a function so you can call it again after a reconnection.
- Add error and 'end' handlers on the provider, persist the last processed block, and perform a catch‑up query on reconnect.
- Verify end‑to‑end by sending a transaction (e.g., via Hardhat, Truffle, or a simple
web3.eth.sendTransaction) and confirming that your callback logs the expected data.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.