Webhook reliability decides whether your automations can be trusted. A payment succeeds but the order is never marked paid. A customer is charged twice because a retry ran the same handler again. A shipment update arrives before the order exists and is silently dropped. None of these are exotic bugs; they are what happens when event-driven integrations are built as if the network were perfect.
As businesses connect more systems, payment gateways, WhatsApp, ERPs, CRMs, e-invoicing portals and AI agents, more of their operations run on events passing between services. The patterns that make this reliable are well understood: at-least-once delivery, idempotent handlers, retries with backoff, ordering tolerance, signature verification, the transactional outbox and proper observability.
This guide explains each pattern in plain terms, with checklists you can hand to your development team or automation vendor.
How webhooks actually behave
A webhook is an HTTP request one system sends to another when something happens. The sender expects a fast 2xx response. If it does not get one, it retries.
Stripe's webhook documentation is a useful reference because it states the realities explicitly, and most providers behave similarly:
- Retries: in live mode, Stripe retries failed deliveries for up to three days with exponential backoff.
- Duplicates: endpoints might occasionally receive the same event more than once; Stripe recommends logging processed event IDs and skipping repeats.
- No ordering guarantee: events are not guaranteed to arrive in the order they were generated, so your handler must not depend on order.
- Respond quickly: return a 2xx before any complex logic that might cause a timeout, and process events through an asynchronous queue.
- Verify signatures: each event is signed with HMAC SHA-256 and a timestamp, and libraries reject events older than a tolerance (five minutes by default) to prevent replay.
That combination, at-least-once, unordered, retried delivery, is the normal case, not the exception. Design for it.
Idempotency: the core pattern
An operation is idempotent if doing it twice has the same effect as doing it once. For webhooks, that means:
- Pick an idempotency key: usually the provider's event ID. Where providers may emit two separate events for the same change, combine object ID and event type.
- Record before acting: insert the key into a processed-events table with a unique constraint, in the same database transaction as the business change.
- Skip duplicates: if the insert fails because the key exists, return 2xx and do nothing.
- Make downstream calls idempotent too: when your handler calls another API (issue a refund, create an invoice, send an e-invoice to the IRP), pass an idempotency key derived from the original event so a retry does not create a second refund or a second document.
- Keep keys long enough: at least as long as the sender's retry window, and longer for manual replays.
| Operation | Naive version | Idempotent version |
|---|---|---|
| Mark order paid | Set status and send confirmation | Check event processed; set status only if not already paid; send confirmation once |
| Add loyalty points | points = points + 100 | Insert a points transaction keyed by event ID |
| Create shipment | Call courier API | Call with idempotency key; store shipment ID against order |
| Generate e-invoice | Submit to IRP | Check for existing IRN on invoice before submitting |
Ordering: design for events arriving out of sequence
Out-of-order delivery breaks state machines. An "invoice.paid" event can arrive before "invoice.created". Options:
- Fetch current state: treat the event as a notification and fetch the latest object from the source API. Stripe's newer "thin" events are designed around this.
- Use versions or timestamps: ignore updates older than what you already have, using a version number from the source where available rather than coarse timestamps.
- Make transitions tolerant: allow "paid" to be recorded even if "created" has not been processed yet, and reconcile later.
- Park and retry: if a dependency is missing, put the event back on a queue with a delay instead of failing.
Retries and dead-letter queues
On the receiving side, separate acceptance from processing:
- Verify the signature, store the raw event, return 2xx.
- A worker processes events from a queue.
- On transient failure (timeout, 5xx from a downstream API), retry with exponential backoff and jitter.
- On permanent failure (validation error, missing reference), move the event to a dead-letter queue and alert a person.
- Provide a replay tool so an operator can reprocess events after fixing the cause.
Sending events reliably: the transactional outbox
When your own system emits events, for example "order paid" to fulfilment and accounting, the classic bug is the dual write: you update the database and then publish an event, and a crash between the two loses the event or publishes one for a change that rolled back.
The transactional outbox pattern fixes this. Write the business change and an outbox record in the same database transaction. A separate relay reads the outbox and publishes to a queue or webhook, marking records as sent. Because the relay can crash after publishing but before marking, it may publish twice, which is fine because consumers are idempotent.
Standard formats and signatures
Two open efforts reduce custom work:
- CloudEvents: a CNCF specification for describing event metadata (source, type, ID, time) consistently across systems. It graduated within the CNCF in January 2024 and is supported by major cloud event services.
- Standard Webhooks: an open specification for webhook headers (webhook-id, webhook-timestamp, webhook-signature) and HMAC signing, with libraries in many languages. If you send webhooks to customers or partners, adopting it saves them work.
Whichever you use, the security basics are the same: HTTPS only, verify signatures over the raw request body, compare signatures in constant time, reject stale timestamps, rotate secrets, and never trust the payload alone for high-value actions; confirm with the source API.
Observability: knowing when things break
Silent failure is the expensive kind. Instrument every integration:
- Structured logs with event ID, type, source, correlation ID and outcome.
- Distributed tracing (OpenTelemetry is the common standard) so one order can be followed across WhatsApp, payment gateway, ERP and courier.
- Metrics: events received, processed, failed, retried, in dead-letter queue, and processing latency.
- Business-level checks: "orders paid in gateway but not in ERP", "invoices approved without IRN after one hour".
- Alerts routed to an owner, with runbooks.
| Signal | Example alert |
|---|---|
| Dead-letter queue growth | More than 10 events in 15 minutes |
| Processing lag | Oldest unprocessed event older than 5 minutes |
| Signature failures | Spike may mean secret rotation issue or attack |
| Business mismatch | Gateway successes not matched to orders after 30 minutes |
| Provider disabled endpoint | Webhook endpoint marked failing by sender |
Where no-code automation tools fit
Tools like n8n, Zapier and Make are excellent for connecting systems quickly. The same rules apply: check whether the tool deduplicates triggers, configure retries, add an idempotency check (a lookup against a table before acting), and set up error workflows that alert someone. For self-hosted n8n, use queue mode and a persistent database for production. Our n8n workflow automation guide covers hosting choices. When volume, money or compliance is involved, consider moving the critical path into code with proper tests, and keep the no-code tool for the surrounding steps.
Applying this to common Indian and UAE integrations
- UPI and card payments: verify gateway signatures and amounts, key on gateway payment ID, reconcile daily. See WhatsApp commerce and UPI payments.
- GST e-invoicing and UAE Peppol: never submit the same invoice twice; check for an existing IRN or document ID before retrying. See e-invoicing automation.
- Hospital integrations: lab results, PACS and insurance updates have the same delivery issues; our HL7 and FHIR integration guide covers healthcare messaging.
- AI agents: an agent calling tools is just another event producer; give its actions idempotency keys too.
A worked example: payment to invoice
Consider an order paid by UPI through a payment gateway, which must update the order, generate a GST invoice and notify the customer.
- The gateway sends a "payment captured" webhook. The endpoint verifies the signature, stores the raw event and returns 200 within milliseconds.
- A worker picks up the event. It inserts the gateway's event ID into the processed-events table in the same transaction that marks the order paid and writes an outbox record "order.paid".
- If the gateway retries the same event, the insert fails on the unique key, and the worker simply acknowledges it.
- The outbox relay publishes "order.paid". The invoicing service consumes it, checks whether an invoice already exists for this order, and only then creates one and requests an IRN, passing the order ID as its own idempotency reference.
- The notification service consumes the same event and sends one WhatsApp utility message, recording the message against the order so a replay does not send a second one.
- A nightly job compares gateway settlements with paid orders and invoices, and alerts finance to anything unmatched.
Each step can fail and be retried safely. That is the whole point: no step assumes the previous one ran exactly once.
Checklist for your next integration
- Endpoint verifies signatures on the raw body and rejects stale timestamps.
- Endpoint stores the raw event and returns 2xx quickly.
- Processing happens asynchronously from a queue.
- Every handler is idempotent with a unique processed-events key.
- Downstream API calls carry idempotency keys.
- Handlers tolerate out-of-order events.
- Retries use backoff with jitter; permanent failures go to a dead-letter queue.
- Outgoing events use the transactional outbox.
- Logs, traces and metrics carry event and correlation IDs.
- Business-level reconciliation runs daily with alerts.
Algon builds integrations this way across Node.js and Python services, on AWS and cloud infrastructure, as part of our full-stack development work.
Conclusion
Reliable automation is mostly about handling the boring failures well: duplicates, retries, out-of-order events and silent errors. Build idempotent handlers, separate acceptance from processing, use the outbox for events you send, verify every signature and instrument everything. With those patterns in place, adding the next integration or AI agent becomes routine rather than risky.
Frequently asked questions
What is webhook idempotency?
Webhook idempotency means processing the same event more than once has the same effect as processing it once. It is usually implemented by storing each event ID with a unique constraint and skipping events already processed.
Why do I receive duplicate webhooks?
Most providers deliver at least once and retry when they do not receive a timely 2xx response, so the same event can arrive multiple times. Some providers may also generate separate events for the same change.
How long do providers retry failed webhooks?
It varies by provider. Stripe, for example, retries live-mode deliveries for up to three days with exponential backoff. Check each provider's documentation and keep idempotency records at least that long.
What is the transactional outbox pattern?
It writes a business change and an outgoing event record in the same database transaction, then a separate relay publishes the event. This avoids losing events or publishing events for changes that rolled back.
How do I verify a webhook is genuine?
Verify the HMAC signature over the raw request body with the shared secret, compare in constant time, reject old timestamps to prevent replay, use HTTPS, and for high-value actions confirm the state with the provider's API.
Are no-code tools like n8n or Zapier reliable for payments?
They can be, with care: add idempotency checks, retries and error alerts. For high-volume or high-value flows, many teams keep the critical path in tested code and use no-code tools for surrounding steps.