Webhook reliability for your own store: retries, idempotency, signature verification, and monitoring

A webhook isn't "fire and forget" — it's a delivery protocol with an acknowledgement. If the other side doesn't return a 2xx within 3 seconds, you have to send it again. Reliable webhooks come down to four things at once: HM...

Webhook 可靠性四要素:收得到、认得准、不重复、看得见Webhook reliability: receive, verify, dedupe, observe 反直觉:Webhook 不是”发了就行”——对方不回 2xx,你就必须重发Counter-intuitive: a webhook is not sent until a 2xx comes back 要素Element 做法How 量化标准Standard 签名验证Signature HMAC-SHA256 校验头HMAC-SHA256 header check 时间戳容差 5 分钟5-min timestamp skew 快速返回Fast ack 先回 2xx 再异步处理Ack 2xx, process async 3 秒内必须响应Respond within 3s 幂等去重Idempotent 事件 ID 落表去重Dedupe by event id 同一事件重试 N 次只处理 1 次Retry N times, process once 重试退避Retry backoff 指数退避 + 抖动Exponential + jitter 最多 5 至 8 次,约 24 小时5-8 retries over ~24h 核心原则:先验证签名,再快速回 2xx,事件入库后异步处理Verify signature, ack fast, then process async 不要在同步请求里做耗时业务,否则对方判定超时开始重发,造成重复Never do slow work inline or retries will duplicate work 监控红线:重试队列堆积超过 100 条或连续 30 分钟无成功投递,立即告警Alert: backlog > 100 or zero success for 30 min 超过重试上限的事件进死信队列,人工补单,不能静默丢弃Dead-letter failed events; never silently drop
The four pillars of webhook reliability: signature verification, fast acknowledgement, idempotent deduplication, and retry backoff with dead-letter monitoring

Webhook reliability for your own store: retries, idempotency, signature verification, and monitoring

Bottom line first: A webhook isn’t “fire and forget” — it’s a delivery protocol with an acknowledgement. If the other side doesn’t return a 2xx within 3 seconds, you have to send it again. Reliable webhooks come down to four things at once: HMAC signature verification to block forgeries, returning a 2xx fast and processing asynchronously, deduplicating by event ID, and retrying with exponential backoff plus a dead-letter queue. The most common incident isn’t “it never arrived” — it’s “it arrived and got processed twice,” or “processing was so slow it triggered a resend, and one order turned into three.”

1. Why webhooks are unreliable by nature

A webhook is the other system POSTing an event to your callback URL on its own initiative. It rides on HTTP, and HTTP is connectionless and doesn’t guarantee delivery: a network blip, a restart of your service, or a processing timeout can lose that delivery. Worse, you don’t know whether the other side received it — you sent it but got no 2xx response. Was it lost on the network, or did they process it and the response got lost?

Here’s the counterintuitive judgment:Webhook systems retry by default, so your receiver has to be designed for “this will be delivered more than once,” not built on the assumption that it arrives a single time. Mature platforms like Stripe and Shopify handle it this way: if the receiver doesn’t return a 2xx, they resend with exponential backoff, up to 5 to 8 times over roughly 24 hours.

Without idempotency, the same payment event gets posted to your books several times. For connecting leads into your system, see Website leads to CRM, which faces the same duplicate-event problem.

Miss any one of the four and reliability drops a notch. The table below lays out what each one does and what happens if you skip it.

Element Role Standard practice What happens if you skip it
Signature verification Confirm the request really comes from them Verify the HMAC-SHA256 header Forged events inject fake orders
Fast acknowledgement Stop them from timing out and resending Return 2xx first, process asynchronously Slow processing causes duplicate deliveries
Idempotent deduplication Process duplicate events only once Write event IDs to a table and dedupe The same order gets posted multiple times
Retry backoff Temporary failures eventually get through Exponential backoff with jitter A transient glitch loses the order outright
Dead-letter monitoring Failed events aren’t dropped silently Over-limit events go to a dead-letter queue with alerts Orders go missing for a long time with nobody the wiser

2. Signature verification: confirm the request really isn’t a forgery

A webhook callback URL is publicly reachable, so anyone can POST data to it. Without signature verification, an attacker can craft a fake “payment succeeded” event and your backend may actually grant access or ship an order. This isn’t a theoretical risk — it’s an attack surface that has been hit in the wild.

The standard approach is HMAC-SHA256: the sender signs the request body with a shared secret and puts the signature in a header (such as X-Signature). When you receive it, you recompute the signature over the raw request body with the same secret and compare the two. Only process the request if they match.

Two details are mandatory. First, compare using a constant-time function, not a plain string ==, to prevent timing attacks. Second, validate the timestamp in the header and reject anything older than a 5-minute tolerance to stop replay of captured requests. And you need to be able to rotate the secret immediately if it leaks.

Check How to do it If it fails
Does the signature match? Recompute and compare with HMAC-SHA256 Reject with 401
Is the timestamp fresh? Within 5 minutes of server time Reject to prevent replay
Has the secret expired? Support overlapping old and new secrets during rotation Retry with the old secret
Has the body been tampered with? Sign the raw bytes Don’t JSON-decode before signing

3. Acknowledge fast, process asynchronously: don’t do heavy work in the request

This is the easiest trap to fall into. Many people receive a webhook and then do a chain of heavy work inside the synchronous request: query the database, send email, call third-party APIs, write logs — five seconds all told. The sender waits three seconds, gets no 2xx, marks it as timed out, and starts resending. Meanwhile you’re still processing the first one, and the second one has already arrived.

The right pattern has three stages: on receiving the request, verify the signature (milliseconds), then immediately persist the event as-is and return 2xx, and let a background job process it slowly. That way the sender always gets an acknowledgment within three seconds and won’t resend. The heavy work can take its time — the user already has a receipt. Failed events stay in the queue and the async job retries them on its own.

Example scenario: A payment-succeeded event comes in. The receiver does only three things: verify the signature, write the event ID and raw payload to an events table, and return 200 immediately. The real work — adding inventory, sending email, notifying ERP — is done slowly by a background worker that pulls pending events from the events table. Even if sending email takes 10 seconds, it won’t trigger a resend. For a similar approach to form-delivery reliability, see Inquiry Form Reliability.

Stage Action Time target On failure
Synchronous receive Verify signature + persist + return 2xx Under 300 milliseconds Reject immediately if verification fails
Async processing Query database, send notifications, call downstream services Seconds to minutes is fine Failures stay in the queue for retry
Retry Re-run at backoff intervals Exponential backoff Move to dead-letter queue past the limit
Manual recovery Check the dead-letter queue and trigger manually Daily inspection Log the reason for manual recovery

4. Idempotency and retries: the same event ten times is still processed once

Idempotency means running the same operation once or ten times produces exactly the same result. Webhooks must be idempotent by nature, because you can’t stop the sender from resending. The fix is simple: every event the sender fires carries a globally unique event ID. Before you process anything, look up that ID in your events table. If you’ve seen it, just return success and skip the work.

Use exponential backoff with jitter for retries: wait 1 minute after the first failure, 2 minutes after the second, 4 minutes after the third, doubling each time, up to 5 to 8 attempts spanning roughly 24 hours. The random jitter keeps every failed event from retrying in the same second and crushing your service all over again (this is called a retry storm). Events that exceed the retry limit aren’t dropped; they go to a dead-letter queue for someone to handle.

Retry attempt Backoff interval (approx.) Elapsed time Description
Attempt 1 1 minute 1 minute Temporary network blip
Attempt 2 2 minutes 3 min read Brief service restart
Attempt 3 4 min read 7 min Downstream temporarily unavailable
Attempt 4 8 min read 15 min Longer outage
Attempts 5 to 8 15 to 30 minutes About 24 hours Still failing, goes to dead-letter queue

5. Monitoring and dead letters: don’t let lost orders happen silently

The most insidious webhook failure is the silent lost order: the sender thinks it delivered, you have no record at all, and weeks later a customer complains “I paid, why hasn’t it shipped?” before you realize the event never arrived. So your monitoring has to watch three things: delivery success rate, retry queue backlog, and dead-letter queue count.

Set concrete thresholds: if the retry queue backs up past 100 events, or no delivery succeeds for 30 straight minutes, alert your usual channels immediately. Check the dead-letter queue once a day and manually reprocess anything stuck there. If your delivery success rate drops below 99%, investigate. A healthy webhook success rate should be close to 99.9%; falling below 99% means you have a persistent problem. For how to set up general monitoring and alerts, see Site monitoring and alerts.

Metric Health score Alert threshold Action
Delivery success rate ≥ 99.9% Below 99% for 1 hour straight Check receiver logs
Retry queue backlog Close to 0 Over 100 events Check whether downstream is stuck
Dead-letter queue 0 Any backlog at all Reprocess manually and find the cause
No successful deliveries Events keep coming 0 for 30 minutes Check whether the callback URL is blocked
Signature verification failures Close to 0 10x spike Someone may be scanning your endpoint

FAQ

Why do I sometimes get duplicate order events on my webhook?
Because the sender automatically resends when you don’t return a 2xx in time. Mature platforms resend up to 5 to 8 times by default, spanning about 24 hours. The answer isn’t to stop the resends, it’s to deduplicate on your side by event ID: when an event arrives, check the table first, and if you’ve already processed it, return success and skip the business logic. Duplicate delivery is normal for webhooks, so design for it.
Should I process a webhook immediately after receiving it, or return first?
Acknowledge first. Once the signature checks out, write the event to the database and return 2xx within 3 seconds. Do the real work asynchronously in the background. If you send emails, query the database, or call downstream services inside the synchronous request and take 5 seconds, the sender will time out and start retrying, which creates duplicates. Let an async worker handle it slowly. Slow is fine.
Can I skip signature verification since it’s all over HTTPS anyway?
No. HTTPS only protects data in transit from being eavesdropped on. Your callback URL is publicly reachable, so anyone can POST data straight to it. Without signature verification, an attacker can craft a fake payment-success event and trick your backend into shipping goods or granting access. You must verify the signature in the request header with HMAC-SHA256, and add a 5-minute timestamp tolerance to prevent replay attacks.
How should I set the retry interval, and why not just retry every minute?
Use exponential backoff with random jitter: 1 minute, 2 minutes, 4 minutes, 8 minutes, up to 5 to 8 attempts spread over about 24 hours. The problem with a fixed one-minute interval is that when a downstream service fails, all failed events retry in the same second and crush the service just as it recovers. That’s called a retry storm. Random jitter spreads the retries out.
What do I do with events that still fail after many retries?
Never drop them silently. Send them to a dead-letter queue. Store events that exceed the retry limit in a separate table, review it once a day, and after a human confirms, manually trigger a resend and record the failure reason. Silent dropping is the most dangerous approach. Weeks later, when a customer complains they never received their goods, you won’t find any trace in the logs that the event ever arrived.
How do I know if webhooks are silently dropping events?
Watch three numbers: investigate if the delivery success rate drops below 99%, alert if the retry queue backs up past 100 items, and alert immediately if there are no successful deliveries for 30 minutes straight. In a healthy state, the delivery success rate should be close to 99.9%. One more: check every day that the dead-letter queue is at 0. With these alerts in place, silent drops get caught the same day instead of weeks later by a customer.

Final thoughts

The core insight about webhook reliability is this: it isn’t a one-way push, it’s a two-way protocol where no acknowledgment means a resend. When you design the receiving end, you must treat duplicate deliveries, forged events, and lost events as the default assumption, not as low-probability edge cases. Of the four elements, idempotent deduplication and fast acknowledgment are the two most easily overlooked and the two that cause the most incidents. Get those two right first, and your incident rate drops by more than half.

Three steps to make it work. First, the receiving endpoint does nothing but verify the signature, write to the database, and return a 2xx within 3 seconds — everything else runs asynchronously. Second, build a dedup table keyed on event ID, and retry with exponential backoff plus jitter. Third, review the dead-letter queue daily, and wire delivery success rate and backlog size into alerts. Do these three things and your callback pipeline goes from “orders occasionally go missing and you hope for the best” to “if one goes missing, you know the same day and can recover it.”

One line worth remembering: webhook reliability isn’t about the sender sending reliably — it’s about you receiving dumbly. Verify the signature, respond fast, dedup aggressively. Better to process something twice than to miss a single order.

Crater
Crater Crater experts

Official Organization · Crater

Crater is a go-global services company specializing in WordPress standalone site development + Google SEO + GEO full-funnel growth, serving sectors including smart hardware, industrial equipment, SaaS, and new energy.

Product CapabilitiesMethodology WhitepaperIndustry Report
965articles shared
78knowledge base articles