Crypto Payment Webhooks: How They Work and How to Build Them
Learn how crypto payment webhooks detect deposits, how to verify signatures, handle retries and duplicates, and test your endpoint before going live.
Reviewed by Maurice Sobiera

A crypto payment webhook is an HTTP request that a blockchain service sends to your server when something happens on-chain, such as a deposit arriving at a customer’s address. Instead of asking the network every few seconds whether a payment arrived, you give the provider a URL and it calls you.
To run this in production you need four things: a public endpoint, signature verification, idempotent handling of duplicate events, and a fallback for missed deliveries. This guide covers each one.
TL;DR
- A webhook pushes an event to your server when a payment or wallet event occurs, so you skip constant polling and the load it puts on your system.
- Verify every request’s signature before you trust any field in it. With Chaingateway the signature covers the transaction ID only, so check amount and recipient yourself.
- Answer with a 2xx fast, then do the real work in a background job.
- Treat every event as possibly repeated: store event IDs and make your order updates idempotent.
- Keep a slow reconciliation job for pending payments. Chaingateway does not retry failed notifications by itself, so a missed delivery would otherwise leave an order stuck.
What are crypto payment webhooks?
A webhook is a callback. You register a URL and tell the provider which events you care about. When one occurs, the provider sends a POST request with a JSON body that describes it. For crypto payments, those events are usually an incoming transfer to a monitored address, an outgoing transfer, or a token transfer on a contract.

The alternative is polling. Your server calls an API on a timer and compares the result with what it saw last time. Polling is simple to write and wasteful to run. Every request that returns nothing new costs you rate limit, compute and latency, and the delay between a payment and your reaction is as long as your interval. With several chains and thousands of customer addresses, the number of calls multiplies quickly.
Webhooks reverse the direction. The provider watches the chain, matches transactions against the addresses you subscribed to, and notifies you once. The reaction time drops to seconds, and your own infrastructure stays idle until there is something to do. We wrote up the longer argument for deposits in how webhooks process deposits automatically.
Webhooks versus polling at a glance
| Criterion | Polling | Webhooks |
|---|---|---|
| Who starts the exchange | Your server | The provider |
| Reaction time | As slow as your interval | Close to real time |
| Wasted requests | Most of them | None |
| Cost grows with | Number of addresses times frequency | Number of actual events |
| Failure mode | Late detection | Missed or repeated delivery |
| Best use | Reconciliation and safety net | Primary signal |
The last row is the practical answer. Webhooks should be the main signal, and polling should be a slow backstop that checks only the payments still pending. Run the check only for invoices that are still pending, so a missed delivery is found within minutes and the request count stays low.
How does a webhook detect a crypto payment?
The provider side is the part you do not see, but understanding it explains most of the behavior you will meet in testing.
- Subscription. You create a wallet or register an address, then register a webhook for it through the provider’s API. The subscription holds your callback URL and filters such as sender, receiver, contract address and transaction type.
- Monitoring. The provider runs nodes for each supported chain and reads every new block. Each transaction is matched against the subscriptions and, for tokens, against contract transfer logs.
- Decoding. Raw chain data is not friendly. A token transfer on Ethereum is an encoded log entry, and Bitcoin has inputs and outputs rather than a sender and recipient. The provider turns this into a readable payload.
- Delivery. The provider posts the payload to your URL. If your server fails to answer with a success code, what happens next depends on the provider. Some retry on a schedule. Chaingateway does not retry by itself: it lists failed notifications, and you ask for each to be sent again.
A Chaingateway notification carries the fields id, webhook_id, from, to, blocknumber, datetime, type, contractaddress, tokenid, txid and amount. The same field names are used on Ethereum, BNB Smart Chain, Polygon, Arbitrum and Tron. Bitcoin webhooks filter on sender and receiver only. Solana has no webhooks. Token standards such as ERC-20, BEP-20 and TRC-20 are supported, which matters because most real-world payments are stablecoin transfers rather than native coin.
Events worth subscribing to
Start narrow. In Chaingateway, a webhook is defined by filters, so each event type maps to a filter combination:
- An incoming transfer to a customer deposit address uses
to. This is the core event for crediting an account or marking an invoice paid. - An outgoing transfer uses
from, for example to see when a payout or sweep reaches the chain. - A token transfer uses
type(such asERC20orTRC20) with acontractaddress. - An NFT transfer uses the
token_idfield with an ERC721 or TRC721 type. - The optional
referencefield, up to 255 characters, lets you tie a webhook to an order or customer in your own system.
A notification announces that a transaction matching the filter was seen. Whether you treat it as final is your own policy, as the section on confirmation depth explains.
Our post on Polygon webhooks without polling shows what this looks like on a single chain.
How do you set up and process a webhook endpoint?
The endpoint is an ordinary HTTPS route on your server. The difference between a demo and a production handler lies in a few habits.
Endpoint requirements
The URL must be reachable from the public internet over HTTPS. Localhost addresses and private networks cannot receive calls, so during development you will want a tunnel that exposes your local port. Keep the route dedicated to webhooks, accept only POST, and do not put it behind login screens or CSRF middleware that would reject a request from a machine.
The handler should respond quickly. The common guidance, for example in the Stripe webhook documentation, is to return a successful status code quickly, before any complex logic that could time out. Successful means a 2xx status as defined in RFC 9110. The same rule applies to crypto events. If you update a database, send an email and call an accounting system before answering, a slow downstream service can make the provider treat the delivery as failed.
A reliable pattern looks like this:
- Read the request body.
- Verify the signature.
- Write the event to a queue or an events table with its ID.
- Return 200.
- Let a worker process the queued event.
How do you verify webhook signatures?
Anyone who finds your URL can post JSON to it. If your handler credits an account because a request says a deposit arrived, a forged request is a theft. Signature verification closes that door.
The typical scheme is HMAC, defined in RFC 2104. The provider computes a keyed hash with a secret that only you and it know, and sends the result in a header. You recompute the hash and compare. Three details trip people up:
- Know what is signed. Many providers sign the whole request body, which means you must verify against the original bytes, not against re-serialized JSON. Chaingateway signs only the transaction ID.
- Compare in constant time. Use your language’s secure comparison function rather than a plain equality check.
- Check a timestamp when the scheme provides one. Rejecting old timestamps limits replay of a captured request. Chaingateway’s scheme has no timestamp, so deduplicate on the transaction ID instead.
Chaingateway sends the signature in an X-Signature header once you have set a personal secret in your profile. The value is the Base64-encoded HMAC-SHA256 of the txid field, keyed with that secret. The signature verification guide has a working Node.js handler, and the documentation has Python, Java and PHP versions. Because only the txid is signed, a valid signature does not prove that amount or to are unchanged. Check them against your invoice, or against the chain, before you credit anything.
Store the secret in an environment variable or a secrets manager, never in the repository, and regenerate it if it ever appears in a log. A regenerated secret takes effect immediately.
How do you handle retries, duplicates and slow confirmations?
Networks fail and deployments restart servers. Your handler will eventually see the same event twice, for example after a resend, and sometimes events out of order. Design for it from the start.
Make processing idempotent
Every event carries something that identifies it. In a Chaingateway notification that is the txid, together with the webhook_id. Save that identifier with a unique constraint before you act. If the insert fails because the key exists, you have seen the event and can return 200 without doing anything.
Then make the business action itself safe to repeat. “Set invoice 1042 to paid with transaction X” is idempotent. “Add 50 to the balance” is not. Prefer the first form, or tie the balance change to the unique event record in a single database transaction.

Respect confirmation depth
A transaction appearing in a block is not the same as a payment you can ship goods against. Chains differ in how quickly a block becomes unlikely to be reversed, and the right threshold depends on the amount and the asset. Define a policy per chain and per order size, and store the state of each payment: detected, confirming, confirmed, failed. The webhook that announces detection should move an order to “payment seen”, not “fulfilled”.
Underpayments, overpayments and wrong tokens
Customers send the wrong amount more often than anyone expects. Your handler should compare the received amount and asset against the invoice and branch: exact match, short, over, or an unexpected token. Decide in advance whether you auto-credit the difference, hold for review, or refund. A decoded payload with the asset and amount as separate fields makes this comparison a few lines of code rather than a parsing exercise.
Keep a safety net
A delivery can fail, and Chaingateway does not retry it. Failed notifications show up in GET /api/v2/<chain>/webhooks/notifications/failed, and you resend each with POST /api/v2/<chain>/webhooks/notifications/{id}/retry. Run a scheduled job, every few minutes, that checks that list and looks at invoices still pending beyond a reasonable time. When it finds a payment your webhook never reported, it feeds it through the same processing path. One code path for both sources means one set of bugs.
How do you test and monitor a webhook endpoint?
Build confidence in layers:
- Local test. Write a small script that signs a sample payload with your test secret and posts it to your endpoint. Confirm that a valid signature passes and a modified
txidfails. - Testnet run. Create a wallet on a test network with the header
X-Network: testnet, register a webhook and send a small transfer from a faucet. You will see the real sequence of events, including the delay before the notification, which no sample payload teaches you. - Duplicate drill. Post the same valid event five times. Your invoice should change once and your logs should show four skipped duplicates.
- Failure drill. Make your handler return 500 on purpose. Then find the notification in the failed list, restore the handler and resend it. It should be processed once.
Once live, log every incoming event ID, the verification result, the processing outcome and the time taken. Alert on three things: signature failures (a misconfigured secret or an attacker), a growing queue (workers falling behind), and invoices pending far longer than normal (silent delivery problems).
Common problems and their usual causes:
| Symptom | Likely cause |
|---|---|
| Signature never matches | Wrong or regenerated secret, or the wrong value was hashed (the txid, not the whole body) |
| No events arrive | URL not public, firewall blocking, address not subscribed, wrong network |
| Same event processed twice | No unique constraint on event ID |
| Notification appears in the failed list | Handler answered slowly or with a non-2xx code |
| Payment credited too early | Business logic acts on detection instead of confirmation |
How do you start receiving payment events with Chaingateway?
You create a webhook with one request to POST /api/v2/<chain>/webhooks. The example registers a TRC20 deposit watcher with a reference to your order:
curl -X POST https://app.chaingateway.io/api/v2/tron/webhooks \ -H "Authorization: Bearer $CHAINGATEWAY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "to": "TWrTTVizHkF3LxTiWE9vCnhWC7CBpNXe22", "contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "type": "TRC20", "url": "https://shop.example.com/hooks/crypto", "reference": "order-10442" }'Chaingateway Webhooks work together with the Blockchain API, so the event that tells you a deposit arrived and the call that sends it onward live under one account.

Plans are published on the pricing page: Plus is €49 per month, Pro €99, Premium €249 and Enterprise €499, or €490, €990, €2,490 and €4,990 per year on annual billing. The 7-day free trial needs no card and no KYC, so you can wire up your endpoint and run the drills above first.
Sources
- Webhooks, Stripe documentation
- HMAC: Keyed-Hashing for Message Authentication, RFC 2104
- Successful 2xx status codes, HTTP Semantics, RFC 9110
Recommended
Frequently asked questions
Yes, but only lightly. Use webhooks as the primary signal and run a scheduled check of payments still pending, so a delivery that never arrived does not leave an order stuck. The check should push any payment it finds through the same processing code as webhook events.
Verify the signature header with your shared secret, and reject requests that fail. With Chaingateway, that means the X-Signature header, the HMAC of the txid and a personal secret that you set in your profile. Never trust the contents of an unsigned request, even if the URL is secret.
Return a 2xx status as soon as you have verified and stored the event. Do slow work such as emails, ledger updates and third-party calls in a background worker. A slow or failing response puts the notification into the failed list, and a resend then delivers it a second time.
It can when your provider normalizes the data. Chaingateway uses the same notification fields on Ethereum, BNB Smart Chain, Polygon, Arbitrum and Tron, so one handler can process events from all of them, with per-chain confirmation rules in your own logic. Bitcoin webhooks filter on sender and receiver only, and Solana has no webhooks.
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.