Building a Reliable Webhook Handler for Crypto Payments: A Technical Guide
Jun, 22 2026
Webhooks are the lifeblood of modern crypto billing. They tell your application when a Bitcoin transaction has been detected, when an Ethereum transfer hits the required confirmation depth, or when a Lightning invoice settles. But unlike traditional card payments where authorization and capture happen in a synchronous handshake, blockchain payments are probabilistic, multi-stage, and often asynchronous.
If you build a webhook handler that simply trusts the incoming JSON payload and immediately ships a digital product, you will eventually face a disaster. Maybe the provider retried the request due to a network glitch, causing you to double-ship. Maybe a chain reorganization reverted a block, invalidating a payment you thought was final. Or maybe a malicious actor spoofed your endpoint, tricking it into releasing goods for free.
Building a reliable webhook handler is a backend system designed to receive, verify, and process HTTP callbacks from external services without losing data or duplicating actions for crypto requires treating every notification as a signal to fetch truth, not the truth itself. This guide breaks down the architectural patterns, security measures, and idempotency strategies needed to handle crypto payments robustly in 2026.
The Core Problem: Why Crypto Webhooks Are Different
In traditional fintech, a webhook usually confirms a state change that has already happened authoritatively within the processor's database. In crypto, the "database" is a decentralized ledger that operates on consensus. This introduces latency and uncertainty.
When a user sends BTC, the transaction enters the mempool. It is unconfirmed. A few minutes later, it gets one confirmation. Then three. Then six. Each of these stages might trigger a separate webhook event from your payment gateway. If your handler treats the first "payment detected" event as final, you risk accepting a double-spend attempt or a transaction that gets dropped by miners due to low fees.
Furthermore, blockchain networks can undergo reorganizations (reorgs). A block containing a valid payment might be orphaned if a competing block wins the race. Your system must be prepared to handle events that indicate a regression in state, such as a previously confirmed payment becoming invalid.
This complexity means your webhook handler cannot be a simple CRUD operation. It must be a state machine capable of reconciling external signals with authoritative on-chain data.
Architectural Pattern: The Async Queue Strategy
The most critical mistake developers make is doing heavy lifting inside the webhook endpoint. Providers like Stripe, Coinbase Commerce, or specialized non-custodial gateways expect a response within 3-10 seconds. If your code takes longer to query a node, update a database, and send an email, the provider will assume failure and retry. This leads to duplicate processing and cascading errors.
The industry-standard solution is the Asynchronous Queue Pattern. Here is how it works:
- Receive & Verify: Your HTTPS endpoint receives the POST request. It immediately verifies the cryptographic signature (more on this below).
- Persist Raw Payload: Save the raw JSON body, headers, and timestamp to a durable database table. Mark the status as 'pending'.
- Acknowledge Quickly: Return a 200 OK status code to the provider instantly. Do not wait for business logic to complete.
- Enqueue Job: Push a job ID or the event ID into a message queue (like Redis Streams, RabbitMQ, or AWS SQS).
- Process Background: A background worker picks up the job, fetches the latest state from the provider’s API or blockchain node, updates the internal record, and executes business logic.
This decoupling ensures your endpoint remains fast and resilient under load spikes, which are common during high network congestion or viral marketing campaigns.
Idempotency: Handling Duplicates Gracefully
Reliable delivery systems use "at-least-once" semantics. This means you might receive the same event twice, three times, or even out of order. Your handler must be idempotent is a property of an operation where performing it multiple times has the same effect as performing it once.
To achieve this, rely on unique identifiers provided by the webhook payload. Most providers include an `event_id`, `delivery_id`, or `invoice_id`. Before executing any business logic, your background worker should check a database table of processed event IDs.
| Step | Action | Outcome |
|---|---|---|
| 1 | Extract event_id from payload |
Get unique key |
| 2 | Query database for existing event_id |
Check history |
| 3 | If exists, log and return success | Skip processing |
| 4 | If new, insert event_id and process |
Execute logic |
This pattern prevents accidental double-charges or double-deliveries. Even if the provider retries ten times due to a timeout, your system processes the action exactly once.
Security: Signature Verification Is Non-Negotiable
Your webhook endpoint is public. Anyone on the internet can find it and send fake POST requests. You must verify that the request actually came from your payment provider.
Providers sign their payloads using a shared secret or asymmetric keys. For example, many services use HMAC-SHA256. They take the raw payload and a timestamp, hash them with your secret key, and send the resulting signature in a header (e.g., `X-Webhook-Signature`).
Your server must reconstruct this hash using the same secret and compare it to the received signature. Use a constant-time comparison function to prevent timing attacks. If the signatures do not match, reject the request immediately with a 401 Unauthorized status.
Additionally, validate the timestamp. Most providers include a timestamp in the header to prevent replay attacks. Reject requests where the timestamp is more than a few minutes old (e.g., > 5 minutes) to ensure you are processing fresh events.
Reconciling State: Fetch Truth, Don't Trust Signals
Treat the webhook as a notification, not the source of truth. When your background worker processes the event, it should query the provider’s REST API or your own blockchain node to get the current status of the invoice or transaction.
For instance, if you receive a `payment_confirmed` webhook for a Bitcoin transaction, your worker should:
- Fetch the transaction details from the provider’s API.
- Verify the number of confirmations against your configured threshold (e.g., 3 for BTC, 12 for ETH).
- Check if the amount matches the invoice exactly (handling overpayments or underpayments).
- Update the local database record to `settled` only if all checks pass.
This approach mitigates risks associated with stale webhooks or provider bugs. If a chain reorg occurs, the next webhook (or a periodic reconciliation job) will detect the discrepancy because the on-chain state will no longer match the webhook’s claim.
Handling Errors and Dead-Letter Queues
Background jobs fail. Database connections drop, APIs time out, or bugs cause exceptions. You need a robust error handling strategy.
Implement exponential backoff for retries. If a job fails, retry it after 1 second, then 2, then 4, then 8, etc. However, set a maximum retry limit (e.g., 5 attempts). If the job still fails, move it to a Dead-Letter Queue (DLQ) is a storage location for messages that could not be processed successfully after multiple retry attempts.
Events in the DLQ require manual inspection. They represent potential revenue leakage or customer service issues. Set up alerts to notify your team when events enter the DLQ so you can investigate and replay them manually once the issue is fixed.
Crypto-Specific Nuances: Volatility and Multi-Chain Support
Crypto payments introduce volatility. An invoice might be generated for $100 USD, equivalent to 0.0015 BTC at the moment of creation. By the time the user pays, the rate might have shifted. Your webhook handler must support events like `invoice_underpaid`, `invoice_overpaid`, or `invoice_expired`.
Modern gateways like TxNod is a non-custodial multi-chain crypto payment gateway that allows merchants to connect hardware wallets via xpubs emit granular events for these scenarios. Your handler should have logic branches for each:
- Underpaid: Notify the customer and offer a top-up option.
- Overpaid: Decide whether to refund the excess or keep it as a tip (based on your business policy).
- Expired: Cancel the order and release any reserved inventory.
Also, consider multi-chain support. If you accept payments on Bitcoin, Ethereum, Polygon, and TON, each chain has different finality times and fee structures. Your state machine must account for these differences. For example, TON transactions settle almost instantly, while Bitcoin might require hours for deep confirmations depending on your risk tolerance.
Logging and Observability
You cannot fix what you cannot see. Log every incoming webhook request, including the IP address, user agent, signature validity, and processing outcome. Structure your logs with correlation IDs so you can trace a single payment across your entire system.
Monitor metrics such as:
- Webhook latency (time from receipt to acknowledgment).
- Processing failure rates.
- Number of retries.
- Events moved to the DLQ.
Tools like Prometheus, Grafana, or Datadog can help visualize these metrics. Set up alerts for anomalies, such as a sudden spike in failed signatures, which could indicate a security breach.
Next Steps for Implementation
Start by defining your state machine. What are the possible states for an invoice? (`created`, `pending_payment`, `detected`, `confirmed`, `settled`, `failed`, `expired`). Map each webhook event type to a state transition.
Choose your tech stack wisely. Node.js, Python, and Go are popular choices for webhook handlers due to their strong async capabilities. Ensure your database supports atomic operations for idempotency checks.
Test thoroughly. Use sandbox environments provided by your payment gateway to simulate various scenarios: successful payments, failures, duplicates, and slow confirmations. Do not rely solely on happy-path testing.
How do I prevent double-processing of crypto payments?
Use idempotency keys. Store every unique `event_id` or `invoice_id` in a database before processing. If the ID already exists, skip the logic. Combine this with an asynchronous queue pattern to ensure quick responses to the provider.
What is the difference between a webhook and polling for crypto payments?
Polling involves your server repeatedly asking the blockchain or provider for updates, which is resource-intensive and slow. Webhooks are push notifications sent by the provider when an event occurs, providing real-time updates with lower overhead.
How many confirmations should I wait for before marking a payment as settled?
It depends on the blockchain and risk tolerance. Bitcoin typically requires 1-6 confirmations. Ethereum usually needs 12-20. Lightning Network payments are considered final instantly. Consult your payment gateway’s documentation for recommended thresholds.
Why is signature verification important for webhooks?
Signature verification ensures the webhook request actually came from the trusted provider and was not tampered with or spoofed by a malicious actor. Without it, anyone could send fake payment notifications to your server.
What should I do if a webhook processing job fails?
Implement exponential backoff retries. If the job continues to fail after several attempts, move it to a Dead-Letter Queue (DLQ) for manual investigation. This prevents infinite loops and ensures no payment is silently lost.