A practical guide to event-driven blockchain architecture using webhooks
If you've ever built a blockchain application, you know the pain. You spin up a loop, poll the chain every few seconds, parse raw blocks, filter transactions, and pray you don't miss anything. It's expensive, fragile, and scales poorly.
There's a better way. Blockchain notifications event-driven webhooks that push updates to your server the moment something happens on-chain.
In this guide, I'll walk through how blockchain notifications work, why they matter, and how to set them up using Tatum's Notification API (v4) with practical code examples you can run today.
What Are Blockchain Notifications?
A blockchain notification is a webhook, an HTTP POST request fired to your server when a specific on-chain event occurs. That event could be:
- An incoming or outgoing token transfer
- A native currency transaction (ETH, MATIC, BTC, etc.)
- An NFT mint or transfer
- A smart contract interaction
- A failed transaction
Instead of your application constantly asking "did anything happen yet?", the blockchain tells you.
Think of it like the difference between refreshing your inbox every 10 seconds vs. getting a push notification when a new email arrives.
Why Not Just Parse Blocks Manually?
You could run your own node, subscribe to new blocks, decode every transaction, and maintain state yourself. Some teams do. But here's what that actually looks like:
- Infrastructure overhead: You need a full or archive node, which requires significant compute and storage.
- Parsing complexity: Raw block data includes every transaction on the network. You need to filter, decode ABI data, handle internal calls, and track token standards (ERC-20, ERC-721, ERC-1155).
- Reliability: Miss a block during a restart? You now have a data gap. You need reorg detection, retry logic, and checkpoint management.
- Multi-chain support: Every chain has its own RPC quirks. Supporting Ethereum, Polygon, Tron, and Bitcoin means four different parsing implementations.
Notifications abstract all of this away. You subscribe once, and the platform handles the monitoring, parsing, and delivery.
How It Works: The 4-Step Flow
Here's the lifecycle of a blockchain notification.
1. Subscribe
You tell the notification service: "Watch this address on this chain, and POST to my webhook URL when something happens."
The response returns an object with a id field, a string identifier for the subscription. Store it.
2. Monitor
The platform begins watching the blockchain. For EVM chains, this means tracking every block for transactions involving your address as sender, receiver, or through internal contract calls. You don't need to do anything here.
3. Receive
When a matching event is detected, your webhook receives an HTTP POST with a JSON payload:
Key fields to note:
typetells you the asset category:native,token(ERC-20),erc721,erc1155,internal, orfeeaddressandcounterAddressIdentify the parties involved (more on this below)txIdlets you look up the full transaction on-chain
4. Unsubscribe
When you no longer need monitoring, delete the subscription using the ID you stored earlier:
Understanding address vs counterAddress
This trips up a lot of developers, so let's be explicit.
For native asset transfers (ETH, MATIC, etc.):
counterAddress= the sender (from)address= the receiver (to)
For token transfers (ERC-20, ERC-721, etc.):
address= the sender (from)counterAddress= the receiver (to)
Yes, they're reversed depending on the asset type. Here's a quick reference:
Native (ETH): address = Receiver (To) · counterAddress = Sender (From)
Token (ERC-20): address = Sender (From) · counterAddress = Receiver (To)
NFT (ERC-721): address = Sender (From) · counterAddress = Receiver (To)
Tron exception: TRC-10 and TRC-20 tokens follow the native asset pattern counterAddress is the sender, address is the receiver.
Supported Chains
Notifications work across a wide range of blockchains. Here are the major ones supported for ADDRESS_EVENT (the broadest type).
- EVM Chains: Ethereum, Polygon, BNB Smart Chain, Arbitrum One, Avalanche, Base, Optimism, Fantom, Cronos, Celo, Chiliz, Klaytn/Kaia, Flare, Berachain, Monad, Unichain, MocaChain.
- UTXO Chains: Bitcoin, Litecoin, Dogecoin, Bitcoin Cash.
- Other: Solana, Tron, XRP Ledger, Tezos.
Each chain is referenced using its network identifier (e.g. ethereum-mainnet, polygon-amoy, bitcoin-testnet, solana-devnet). Both mainnet and testnet variants are available for most chains.
Not all subscription types are available on every chain. For example, internal transaction subscriptions (INCOMING_INTERNAL_TX, OUTGOING_INTERNAL_TX) are only available on EVM chains.
Going Beyond Basic Monitoring
Subscription Types
ADDRESS_EVENT is the catch-all, but you can be more specific:
INCOMING_NATIVE_TXIncoming native currency (ETH, MATIC, SOL, etc.)OUTGOING_NATIVE_TXOutgoing native currencyINCOMING_FUNGIBLE_TXIncoming ERC-20 / fungible tokensOUTGOING_FUNGIBLE_TXOutgoing ERC-20 / fungible tokensINCOMING_NFT_TXIncoming ERC-721 NFTsOUTGOING_NFT_TXOutgoing ERC-721 NFTsINCOMING_MULTITOKEN_TXIncoming ERC-1155 multi-tokensOUTGOING_MULTITOKEN_TXOutgoing ERC-1155 multi-tokensINCOMING_INTERNAL_TXIncoming internal (trace-level) transfersOUTGOING_INTERNAL_TXOutgoing internal (trace-level) transfersPAID_FEEGas fee paymentsOUTGOING_FAILED_TXFailed outgoing transactionsFAILED_TXS_PER_BLOCKAll failed transactions in a blockCONTRACT_ADDRESS_LOG_EVENTSmart contract log events
Using specific types reduces noise and is more efficient than filtering a broad ADDRESS_EVENT subscription.
Each subscription must be unique within your API key. Uniqueness depends on the type:
- Address subscriptions uniquely by
type + chain + address + url - Contract log subscriptions are unique by
type + chain + contractAddress + event + url - Block subscriptions unique by
type + chain + url
Conditions: Filter Before Delivery
Instead of receiving every event and filtering in your application code, you can define conditions at the subscription level. Only events matching all conditions trigger a webhook.
Example: Only notify for transfers above 1 ETH
Example: Only notify for incoming USDT above 1,000 USDT
Available filter fields: value, contractAddress, from, to, tokenId, tokenMetadata.type, tokenMetadata.symbol.
Important: Amount values are always in the chain's smallest unit. 1 ETH = "1000000000000000000" (18 decimals). 1 USDT = "1000000" (6 decimals). Always pass as a string.
Finality Levels
You can control when a notification fires relative to block confirmation:
confirmedFires as soon as the transaction is confirmed. Optimised for speed.finalWaits for full block confirmation depth. Optimised for settlement confidence.
Use confirmed for real-time UX updates. Use final for payment processing or treasury operations where you can't afford a reorg reversal.
Templates: Controlling the Webhook Payload
By default, webhooks use a legacy payload format. But Tatum provides a built-in enriched template that returns structured, human-readable fields with token metadata, and it's the recommended default.
You choose a template by passing templateId when creating a subscription. Available options:
enrichedStructured payload with token metadata (recommended)enriched_with_raw_dataEnriched data plus raw transaction datalegacyOriginal flat format (default if omitted)Custom IDYour own template (see below)
Example: Subscribe with the enriched template
Enriched payload example:
Notice how the enriched payload gives you from/to directly (no more address/counterAddress confusion), includes tokenMetadata with symbol and decimals, and provides additionalData with gas details.
Creating a custom template:
If you need full control over the payload shape, create your own:
This returns a template ID you can then reference in any subscription.
Securing Your Webhooks
A webhook endpoint is a publicly accessible URL. Anyone who discovers it could send fake payloads. Two defenses:
HMAC Verification
When HMAC is enabled, every webhook includes an x-payload-hash header containing a cryptographic digest. Your server reconstructs the hash using your shared secret and compares. If they match:
- The payload wasn't tampered with in transit
- The request was sent by Tatum, not an attacker
This is the recommended approach.
IP Whitelisting
Tatum publishes its IP ranges at ips.tatum.com/ips.json. You can whitelist these in your WAF. This works, but HMAC is more robust since IP ranges can change.
Always Use HTTPS
This should go without saying, but always use HTTPS for your webhook URLs to encrypt payloads in transit.
What Happens When Your Server Is Down?
Webhooks fail. Servers restart. Networks hiccup. Tatum handles this with automatic retries:
- Free plan: 3 retry attempts over a ~2 minute window
- Pay-As-You-Go: 10 retry attempts over a ~24 hour window
- Business: 10 retry attempts over a ~24 hour window
The retry interval follows an exponential backoff formula:
Which produces intervals of roughly: 15s, 44s, 2min, 6min, 19min, 56min, 2hr, 8hr, 24hr.
You can also query past webhook deliveries, including failures, to audit what was sent and what your server responded:
Each record includes the HTTP response code, response body, retry count, and whether the delivery ultimately failed.
Practical Architecture
Here's how this typically fits into a production system:
Blockchain (ETH, BTC, MATIC…) → Tatum Notification Service → Your Webhook Endpoint (POST /webhook) → Message Queue (SQS, Redis, RabbitMQ) → Worker Service (process event, update DB, notify user)
Best practice: Your webhook endpoint should do minimal work to validate the HMAC, enqueue the payload, and return 200 OK immediately. Heavy processing (database writes, user notifications, business logic) happens asynchronously in a worker. This keeps your webhook responsive and prevents timeouts that trigger unnecessary retries.
Quick Start Checklist
- Get an API key from the Tatum Dashboard
- Set up a webhook endpoint using webhook.site for testing
- Create a subscription using the REST API
- Verify delivery by sending a test transaction to your monitored address
- Enable HMAC for production security
- Add conditions to filter noise
- Implement retry handling return 200 quickly, process asynchronously
Wrapping Up
Blockchain notifications replace the fragile poll-parse-filter pattern with a clean, event-driven architecture. You define what you care about, which address, which chain, which event type, under what conditions, and the platform handles the rest.
The result is less infrastructure, fewer missed events, and an application that reacts to the blockchain in real time instead of lagging behind it.
If you're building anything that needs to know when something happens, on-chain payment confirmations, wallet activity monitoring, NFT tracking, and DeFi position management notifications are the foundation to build on.
Building with blockchain notifications? I'd love to hear about your use case. Drop a comment or connect with me.
%20(62).jpg)


.jpg)