Webhook Delivery Guarantees Explained
What webhook providers actually guarantee: at-least-once delivery, retries, no ordering, duplicates. Plus how to build a receiver that survives all of it.
Reviewed by Maurice Sobiera

Webhook delivery guarantees describe how often and in what order a provider delivers events to your endpoint. Most providers promise at-least-once delivery at best. Your endpoint gets most events, sometimes twice, sometimes late and sometimes out of order. Reliable integrations put the safeguards on the receiving side.
This guide explains each guarantee in plain terms, shows what real provider documentation promises and ends with a checklist for any webhook integration, including crypto payment events.
TL;DR
- At-least-once is the realistic ceiling. Providers retry until you return a 2xx response, so duplicates are normal and you must handle them.
- Order is not guaranteed. A timed-out early event can arrive after a later one, so sort by the event’s own timestamp or sequence field.
- Acknowledge first, process later. Verify the signature, store the event durably, return 2xx, then do the heavy work asynchronously.
- Deduplicate on the event ID. Record it with a unique index before any side effect such as crediting a balance.
- Treat webhooks as notifications, not a ledger. Reconcile critical records against the API on a schedule to catch what retries missed.
- Chaingateway does not retry failed notifications automatically. It lists them and re-sends them on request.
What do the three delivery guarantees mean?
Delivery semantics come from distributed systems, and they map neatly onto webhooks.
| Guarantee | What the sender does | What can go wrong | What you build |
|---|---|---|---|
| At-most-once | Sends the event once and never retries | The event is lost if your endpoint is down | Reconciliation against the API |
| At-least-once | Retries until you acknowledge or retries run out | Duplicates, late arrivals, reordering | Idempotent handler, deduplication |
| Exactly-once | Would deliver once, with no loss and no duplicates | Not achievable across an unreliable network boundary | At-least-once plus deduplication |
With at-most-once, a failed attempt means a lost event. It is simple for the sender and unsafe for anything involving money.
With at-least-once, the sender keeps trying until it sees a success response or exhausts its retry budget. Loss becomes rare, but there is a catch. Say your handler credits an account, then crashes before it returns 200. The sender never saw the acknowledgment, so it sends the event again. From the sender’s side that is correct behavior. From yours, it is a double credit unless you prevent it.
Exactly-once is what everyone wants and nobody can promise over HTTP. The sender cannot know whether your server processed the request when the connection drops. The practical substitute is at-least-once delivery combined with a receiver that ignores repeats. That combination gives you exactly-once effects, which is what actually matters.
What do providers actually promise?
Read the delivery section of any serious webhook provider’s docs and the same pattern appears.
Adobe I/O Events says that each event is delivered at least once per registration, that duplicates may be sent, and that event order is not guaranteed. Column, a US banking API, states that webhook events are not guaranteed to arrive in creation order. A timed-out earlier event can land after a later one because each retry is rescheduled independently.
That last detail explains most ordering bugs. Event A fails once and is scheduled for a retry in a minute. Event B, created afterward, succeeds immediately. Your system sees B, then A. Nothing is broken on the provider’s side. The guarantee simply never covered order.
Order: use the event’s clock, not the delivery clock
Wise’s webhook guidance recommends rebuilding chronology from event timestamps or, where available, monotonically increasing sequence IDs, rather than from when the notification was dispatched. Its documentation notes that the sent-at time can differ from when the event occurred, especially during retries.
In practice:
- Store the event’s business timestamp or sequence number alongside the payload.
- When an event arrives, compare it with the latest state you hold for that object.
- If the incoming event is older than what you already applied, record it but do not let it overwrite newer state.
For a payment status that moves from pending to confirmed, this stops a late “pending” event from rolling a confirmed payment back.
How do webhook retries work, and when do they stop?
Retry policies vary, but the shape is consistent: exponential backoff, a cap and a hard stop. Chaingateway is an exception, because it does not retry automatically.
| Provider | Retry behavior (from its documentation) |
|---|---|
| Column | Up to 25 retries within three days, exponential backoff, first retry one minute after the failed delivery |
| Probo | Up to 12 attempts, exponential backoff with full jitter from a 30-second base, capped at four hours |
| Stripe | Exponential backoff in live mode for up to three days |
| Chaingateway | No automatic retry. Failed notifications are listed at GET /api/v2/{chain}/webhooks/notifications/failed and re-sent with POST /api/v2/{chain}/webhooks/notifications/{id}/retry |
Sources: Column, Probo and Stripe documentation, read on 5 November 2026, and the Chaingateway OpenAPI specification.
Probo’s docs add useful detail. It retries network errors, timeouts, and 408, 425, 429 or 5xx responses, and it honors a Retry-After header up to its cap. Once the attempts are exhausted, the delivery is marked failed and is not re-queued.
Two lessons follow. First, your endpoint’s error codes are signals: a 5xx tells the sender to try again, so return one only when you actually want a retry. Second, the retry window is finite. If your service is down longer than the window, those events are gone from the retry queue and you need another way to recover them.
Why should I acknowledge fast and process later?
A delivery counts as successful when you return a 2xx status. Column considers an event delivered when it receives a 2xx within 10 seconds and recommends returning it right after receipt, before substantial processing. NestJS’s documentation treats 2xx as success and everything else, including redirects, timeouts and refused connections, as failure.
So the handler’s job is narrow:
- Read the raw body.
- Verify the signature.
- Check the event type and ID.
- Write the event to durable storage (a database row or a queue message).
- Return 200, 202 or 204.
Everything else, such as updating balances, sending emails or calling other services, runs from a worker that reads from that storage. If you do the slow work inline, a slow database query becomes a timeout, the timeout becomes a retry, and the retry becomes a duplicate. The ingest, queue and fan-out approach is covered in our own article on webhooks vs WebSockets.
How do I make duplicates harmless?
Because at-least-once delivery means repeats, your handler must be idempotent. The standard method is simple.
Probo’s guidance is to verify the signature, confirm the expected organization and event type, then atomically record the event ID with a unique index before any side effects. A later request carrying an ID you already recorded is ignored. Stripe gives the same advice: record processed event IDs and skip any event already handled. Many providers make this easy by including a unique ID in every payload. Adobe, for example, puts a UUID in the payload and repeats it in an x-adobe-event-id header.
A minimal pattern:
- Insert the event ID into a table with a unique constraint.
- If the insert fails because the ID exists, return 200 and stop.
- If it succeeds, enqueue the work.
The order matters. Record first, act second. If you act first and crash before recording, the retry repeats the action. If you record first and crash before acting, your worker should pick up unfinished records on restart, so the event is not silently dropped.
For money movements, add a second layer: make the business operation idempotent too. Key a deposit credit on the transaction hash and log index or the payment ID, so even a bug in the event table cannot credit the same payment twice.
How do I verify every request?
A webhook endpoint is a public URL that triggers actions in your system. Anyone can post to it unless you check who sent the request.
Stripe requires verification using the raw request body, the signature header and your endpoint secret. Probo’s advice is to use the untouched body and verify the signature before parsing the JSON, then apply a timestamp freshness check. Parsing and re-serializing the body before verifying is a classic cause of signatures that never match.
A solid verification routine for providers that sign the body:
- Compute the HMAC over the exact bytes received, using your secret.
- Compare it to the header value with a constant-time comparison.
- Reject requests whose timestamp is outside a short tolerance window to limit replayed captures.
- Only then parse the JSON and look at the event type.
Chaingateway works differently. It computes a Base64 HMAC-SHA256 over the txid from the payload and sends it in the X-Signature header. The signature documentation shows the check, and our post on the webhook security feature explains the setup.
How do I recover events that retries missed?
Even a perfect receiver loses events in some scenarios: an outage longer than the retry window, a misconfigured firewall, a deploy that returned 500 for hours. Recovery options differ by provider. Probo has no manual replay and does not re-queue failed deliveries; it expects you to alert on failures and reconcile against its API. Stripe notes that operators can manually process a backlog to speed up recovery. Column says missing or additional events can be fetched through its event APIs when failures require reconciliation.

The common design principle, stated plainly by ThriveDesk’s developer docs, is to treat webhooks as notifications rather than as the only event ledger. Persist payloads durably, process them asynchronously, and periodically reconcile critical records with the REST API.
A reconciliation job can be small:
- On a schedule, list the objects that matter (payments, wallets, orders) from the provider’s API for the last window.
- Compare their status with your own records.
- Apply any differences through the same idempotent code path your webhook handler uses.
- Alert if the job finds gaps often, since that points to an endpoint problem.
Because a later event often carries the full current state of an object, a missed intermediate event may be overwritten by the next one anyway. Reconciliation covers the cases where there is no next one.
How do these guarantees apply to crypto payment events?
On-chain payments raise the stakes, since a missed or doubled event is a missed or doubled credit. A few habits help regardless of provider:
- Key credits on chain identifiers. Use the transaction hash plus the transfer index within it, not the webhook’s delivery ID alone.
- Model status as a progression. Pending, confirmed and final are states with an order. Never let an older status overwrite a newer one.
- Match on your own data. Look up the receiving address or reference in your database before crediting; ignore events for addresses you do not own.
- Reconcile balances. A scheduled comparison of your ledger against wallet balances from the API catches anything the event stream dropped.
Our walkthrough on processing deposits automatically with webhooks puts these steps in a payment flow, and the Polygon webhooks guide shows the event-driven alternative to polling on a specific chain.
Our perspective
We build webhooks for developers who move money, so we think about delivery guarantees every day. Our view is that no honest provider can promise exactly-once delivery, and the right response is a clean contract: events are notifications, signed requests prove where they came from, and the API is always there as the source of truth.
That is why we pair real-time event notifications with a unified REST API across chains. Webhooks tell you something happened. The API lets you confirm it, fetch decoded transaction data, and reconcile when you need to. Our webhooks are HMAC-signed so your receiver can verify them, and the payload carries the decoded sender, recipient, token contract and amount, so your handler spends its time on business logic instead of parsing raw logs.
We also think teams should not have to run their own nodes or juggle chain-specific endpoints to get there. One integration, one signature scheme and one way to look up state across Ethereum, Bitcoin, Tron, Solana, Polygon, BNB Chain and Arbitrum means fewer places for a delivery edge case to hide.
Bitblade
Build on signed, multi-chain webhooks
If you are adding crypto payments to an app, Chaingateway Webhooks deliver blockchain event notifications with HMAC signatures, backed by one Blockchain API for wallets, transfers, decoded transactions and gas prices. Plans on the pricing page start at €49 per month for Plus, with Pro at €99, Premium at €249 and Enterprise at €499 per month, or yearly billing from €490.

Pair the webhook stream with the reconciliation pattern above and you get fast notifications with a safety net.
Sources
- Adobe I/O Events FAQ
- Column: Events and Webhooks
- Wise Platform: Event ordering
- Probo Docs: Webhook Delivery and Recovery
- Stripe Documentation: Webhooks
- NestJS: Webhooks
- ThriveDesk Developer Docs: Retries, failures, and replay
Recommended
Frequently asked questions
Not across an unreliable HTTP connection. The sender cannot tell whether your server processed a request when the response is lost. The practical approach is at-least-once delivery plus deduplication on the event ID, which produces exactly-once effects in your system.
Return a 2xx code such as 200, 202 or 204 as soon as you have verified and durably stored the event. Providers like Column count a delivery as successful only on a 2xx within 10 seconds. Return 5xx only when you want the sender to retry.
Retries are scheduled independently per event. If event A times out and event B succeeds, B arrives first, and A shows up after its retry. Providers such as Adobe and Column say plainly that order is not guaranteed, so sort by the event’s timestamp or sequence field.
Most providers retry with exponential backoff for a limited window, up to about three days in the documentation we reviewed. After that, the event may not be re-queued. Chaingateway does not retry automatically. It lists failed notifications and re-sends them when you ask. In both cases, run a scheduled reconciliation against the provider’s API to recover anything missed.
Yes. Your endpoint is publicly reachable, and a signature check using the raw body and your secret is how you know a request came from the provider. Add a timestamp tolerance to reduce replay risk.
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.