Smart Contract Events API: Read, Filter and Subscribe
How a smart contract events API works: logs, topics and ABI decoding, past events versus live ones, and when to use webhooks, WebSockets or polling.

A smart contract events API lets your application find out what a contract did without scanning every transaction itself. You query past logs by contract address and event signature, then subscribe to new ones as blocks arrive. You can do both through raw node RPC, through a library such as ethers.js, or through a hosted API that decodes the logs and pushes them to you as webhooks. This guide covers how each path works, where each one breaks, and how to choose.
TL;DR
- A contract event is a log entry written when a transaction is mined. Your app reads it afterward. The contract itself cannot read it.
- Past events come from a log query (
eth_getLogsorqueryFilter). Live events come from a subscription or a webhook. Node subscriptions do not replay history. - Only indexed parameters can be used as filters. Decoding needs the contract ABI.
- WebSocket subscriptions die with the connection and can be dropped when you process too slowly. Webhooks with a queue and idempotent handlers survive restarts better.
- A hosted API removes the node, ABI and reconnect work. That is what Chaingateway does for payments and token events.
Table of Contents
- What a smart contract event is
- What is inside an event log
- Reading past events
- Subscribing to new events
- Reorgs and confirmations
- Webhooks, WebSockets or polling
- Setting up event monitoring step by step
- Events across chains
- Build it yourself or use a hosted API
- Our perspective
- Getting events without running nodes
- FAQ
- Sources
What a smart contract event is
Events are how a contract talks to the outside world. According to ethereum.org, events let a contract communicate with a frontend or other subscribing applications, which can process the logged information after the transaction is validated and added to a block.
Three consequences follow, and they explain most of the confusion in the search results:
- Events are output, not storage you can query from Solidity. Contract code writes logs but cannot read them back. That is why threads asking how a contract can access past events always end with “do it off-chain”.
- Events appear only after inclusion. There is no log for a transaction still in the mempool.
- Events are cheap to emit and cheap to search, which is why ERC-20
TransferandApprovalevents, NFT mints and DEX swaps are all tracked through logs and not through state reads.
A smart contract events API is anything that gives your backend structured access to those logs: a node’s JSON-RPC, a library wrapping it, an indexer, or a hosted service that sends you webhooks.
What is inside an event log
To filter or decode logs correctly you need to know their shape. Per the Solidity ABI specification, a log holds the contract address, up to four topics and a data segment. Anonymous events omit the event signature hash.
For a normal event:
- Topic 0 is the Keccak-256 hash of the event signature, for example
Transfer(address,address,uint256). - Topics 1 to 3 hold the indexed parameters.
- Data holds the non-indexed parameters, ABI-encoded.
The ABI separates parameters into indexed ones, stored as hash topics for efficient filtering, and non-indexed ones, stored as binary data in the log.
There is one trap worth knowing. The same specification (in the v0.8.3 edition) notes that dynamic-length indexed parameters, such as strings and byte arrays, are stored as a Keccak hash of a special encoding rather than as the value. You cannot recover the original string from the log. You can only test whether a candidate value matches.
A concrete example: ERC-20 Transfer
A token transfer log has topic 0 for the Transfer signature, the sender address in topic 1, the recipient in topic 2 and the amount in the data field. If you want every deposit to one customer address, you filter on topic 0 and topic 2. We walk through this in detail in our ERC-20 Transfer event guide.

Reading past events
Historical reads are a query, not a subscription. The standard JSON-RPC method is eth_getLogs. It takes a filter object with a block range, an optional contract address and an optional array of topics. The Coinbase Developer Documentation reference for eth_getLogs shows the shape: fromBlock, toBlock and topics, and it notes that a block hash can replace the range.
Libraries wrap this. In ethers.js, contract.queryFilter(event, fromBlock, toBlock) returns matching events, and the ethers v5 contract documentation states that only indexed parameters can be filtered. A parameter left null matches any value.
Practical rules for historical queries:
- Always bound the range. Many providers cap the block span or the number of results per call. Page through ranges in chunks (for example a few thousand blocks at a time) and halve the chunk when you get an error.
- Filter by address and topic 0 at minimum. Unfiltered log queries are the fastest way to hit a rate limit.
- Store the last block you processed. This checkpoint is what lets you resume after a crash without gaps or duplicates.
- Decode with the right ABI. If you only have topic 0, you can look up the event signature, but a proxy contract may emit events defined in its implementation, so use the implementation ABI.
If you want a refresher on talking to nodes directly, our JSON-RPC guide with PHP, Python and JavaScript examples covers the request format.
Subscribing to new events
The live side works differently, and this is where many first implementations fail.
The go-ethereum real-time events documentation explains that subscriptions use persistent WebSocket or IPC connections, typically through eth_subscribe, and stream events as the node processes them. Two limits matter:
- No replay. Subscription notifications cover current events, not past ones. If your process was down for ten minutes, the subscription does not hand you what you missed. You backfill with a log query.
- Connection coupling. Subscriptions are tied to one connection. When it closes, all its subscriptions close.
The same page adds that nodes buffer notifications, and if your client cannot process them fast enough the connection may be closed once the buffer hits a limit (Geth’s example is 10,000). A slow handler that does database writes inline is exactly how that happens.
The pattern that holds up
Combine both mechanisms:
- On startup, read your checkpoint block.
- Backfill from the checkpoint to the current head with
eth_getLogs, in chunks. - Open the subscription, and deduplicate by transaction hash plus log index.
- Hand each event to a queue and acknowledge immediately. Process it in a worker.
- On disconnect, go back to step 1.
This stays correct even when the connection drops, which it will.
Reorgs and confirmations
A log you saw in the latest block can vanish if the chain reorganizes. For anything involving money, do not credit a user on the first sighting. Wait for a confirmation depth that suits the chain and the amount, and handle the “removed” flag that Ethereum-style nodes attach to logs from orphaned blocks. Keep the processed event ID, and make your handler idempotent so a replayed or corrected event cannot credit twice.

Webhooks, WebSockets or polling
All three can deliver events. They differ in who holds the connection and who owns the failure modes.
| Approach | How it works | Strength | Weakness |
|---|---|---|---|
| Polling | Your code calls eth_getLogs on a timer from the last checkpoint | Simple, stateless, easy to resume | Latency equals your interval, and costs requests |
| WebSocket subscription | Persistent connection streams logs from a node | Lowest latency | Dies with the connection, no replay, buffer limits |
| Webhook | A service watches the chain and sends an HTTP POST to your endpoint | No long-lived connection to maintain, works with serverless | You need a public endpoint, signature checks and duplicate handling |
Polling is fine for low-volume back-office jobs. WebSockets suit a dashboard or bot that must react within a block and can tolerate gaps. Webhooks fit payment flows, where you want one reliable push per event and a worker queue behind it. Our write-up on the ingest, queue, fan out pattern for webhooks versus WebSockets goes through the trade-offs in more depth.
Handling webhook delivery properly
Whatever sends the webhook, build the receiver the same way:
- Verify the signature before parsing the body.
- Return a 2xx quickly and process asynchronously.
- Make handlers idempotent, keyed on transaction hash and log index.
- Expect retries and out-of-order delivery.
- Log failures so you can reconcile with a log query later.
Retry behavior is a design topic of its own. For a deeper look at designing around retries and missed notifications, see this piece on payment webhook retry handling.
Setting up event monitoring step by step
Here is a minimal path for a single EVM contract, whichever tool you use.
- Get the ABI. From the verified contract source or your own build output. Without it you see raw topics and hex data.
- Pick the events. List the signatures you care about, such as
Transfer,Approvalor your ownOrderPaid. - Decide the filters. Contract address, topic 0, and any indexed parameter such as the recipient. Remember only indexed values can be filtered.
- Backfill. Query from the deployment block, or from your business start date, in chunks.
- Go live. Subscribe over WebSocket, or register a webhook.
- Decode and normalize. Convert hex amounts using the token’s decimals, and store block number, transaction hash, log index and contract address.
- Wait for finality. Mark events as pending, then confirmed.
- Monitor the monitor. Alert when no event arrives for longer than the chain’s normal gap, or when your checkpoint falls behind head.
Events across chains
The EVM model of topics and ABI-decoded data applies to Ethereum, Polygon, BNB Chain and Arbitrum, so one decoder works across them with different RPC endpoints and confirmation rules. Other networks do not share the model. Bitcoin has no contract logs at all, so you watch addresses and transactions. Tron and Solana have their own formats and APIs. Some Cosmos-style stacks differ again: Tendermint v0.36 deprecated its legacy streaming API in favor of an event-log API that clients query by long-polling with cursors.
The practical upshot is that a multi-chain product ends up maintaining one adapter per network family, each with its own reconnect, decoding and finality code. That is the part most teams underestimate. For a map of the options, see our comparison of six types of blockchain APIs and the explainer on blockchain API versus running a node.
Build it yourself or use a hosted API
Running your own stack means a node (or a paid RPC endpoint), a log scanner, an ABI registry, a reorg handler, a queue and an alerting setup. That is reasonable if events are your core product or you need custom indexing across thousands of contracts.
It is less reasonable if events are a means to an end, such as crediting a deposit or confirming an order. In that case you want decoded events delivered to an endpoint you control, and nothing else to operate.
Our perspective
Most event-handling bugs we see are not about reading logs. They are about what happens around them: a missed notification during a deploy, a duplicate credit after a retry, a decimal conversion that was wrong for one token, a connection that quietly died overnight.
So we build around three beliefs. First, the application should receive decoded, structured data, not raw topics it has to interpret. Second, push beats poll for payments, as long as every message is signed and can be verified. Third, a developer should not have to run nodes or keep chain-specific endpoints in sync just to learn that a payment landed.
That is why Chaingateway exposes one REST API across Ethereum, Bitcoin, Tron, Solana, Polygon, BNB Chain and Arbitrum, with pre-decoded transaction data, automatic gas estimation and HMAC-signed requests. Webhooks sit on top, so your handler gets the event and your team keeps its attention on the product. Whatever you choose, keep the checkpoint, the idempotency key and the confirmation rule. Those three decide whether your numbers are right.
— Bitblade
Getting events without running nodes
If your goal is payment and token events in an app, our Chaingateway Webhooks deliver real-time blockchain notifications to your endpoint, and the Blockchain API gives you decoded transaction data, wallets, transfers and fee estimates through one interface. Plans start at €49 per month for Plus, with Pro at €99, Premium at €249 and Enterprise at €499 per month. Annual billing is €490, €990, €2,490 and €4,990 respectively. See the pricing page for what each tier includes. For a chain-specific example, read about Polygon webhooks without polling.
Sources
- Anatomy of smart contracts, ethereum.org
- Contract ABI Specification, Solidity v0.8.22
- Contract ABI Specification, Solidity v0.8.3
- Real-time Events, go-ethereum
- eth_getLogs, Coinbase Developer Documentation
- Contract, ethers v5 documentation
- Subscribing to Events, Tendermint Core
Recommended
Frequently asked questions
No. Contracts can emit logs but cannot access them afterward, so event data is meant for off-chain consumers. If your contract needs historical data on-chain, store the value in contract state instead, at higher gas cost.
Run a log query from your last processed block to the current head. Node subscriptions only send current events, so reconnecting does not replay anything. Store a checkpoint after each processed block and deduplicate by transaction hash and log index.
Only indexed parameters are stored as topics, and only topics can be filtered. Non-indexed values sit in the data field, so you must fetch the logs and filter them in your own code. If you control the contract, mark the fields you will search by as indexed, up to three per event.
For payments, webhooks with a queue are usually easier to operate, because you do not hold a connection that can drop. Use WebSockets when sub-second reaction matters and you can backfill gaps. In both cases, process events idempotently and wait for enough confirmations before crediting funds.
Ready to build this yourself? Get your API key — 7-day trial, no card required — or see the Blockchain API for the full endpoint reference.