Catching Invalid Webhook Integrations Early
A webhook is a request one service sends to another over HTTP, the protocol used to transfer data on the web, when…
When a service sends your code a webhook, it sends an HTTP request to report an event, such as a record changing. A delivery guarantee describes what the sender promises to do with that request: which response counts as success, whether it will retry a failure, and when it stops trying.
Providers set different policies, so “webhooks are retried” does not mean every event will eventually reach your code. A successful HTTP response also does not prove that your code finished the related work. Knowing where the sender’s responsibility ends helps you decide what your receiver must store, retry, and reconcile.
The sender judges a delivery by what it can observe: whether the receiver responds within the sender’s deadline and with a status code the sender accepts. A 2xx status code indicates success under many webhook protocols. That acknowledgement tells the sender to stop treating the request as failed; it does not, by itself, prove that a downstream action finished.
A receiver can respond quickly after placing an event in a queue, a store of work to process later. This helps when the work takes longer than the sender’s timeout. Before responding, the receiver must make sure it has safely recorded the event or placed it in the queue. Shopify, for example, recommends prompt acknowledgement and documents a one-second connection timeout and a five-second timeout for the full request. These limits are specific to Shopify; other webhook senders set their own limits, as its delivery guidance explains.
Best-effort delivery means the provider attempts delivery according to its policy, but does not promise that every event will reach the receiver and be processed. The policy might include automatic retries, an option to ask the provider to send an event again (manual replay), or no automatic retry at all.
GitHub says it does not automatically redeliver failed webhook deliveries; an operator must arrange recovery, such as manually redelivering a failed event. Shopify documents automatic retries for failed calls, up to eight retries over four hours. It also says an Admin API-configured subscription is automatically deleted after eight consecutive failures. Because Shopify’s documentation uses both “up to eight retries” and “eight consecutive failures,” do not treat those phrases as a definitive count of total HTTP attempts. These are provider-specific policies, not a shared webhook standard. See GitHub’s failed-delivery policy and Shopify’s retry guidance.
A retry window is the period in which a provider attempts automatic recovery. It is separate from how long the provider keeps delivery records or allows manual replay. GitHub, for example, documents manual redelivery for deliveries from the past three days. That recovery period does not mean GitHub automatically retries for three days. Check the provider’s documentation for your specific webhook product and configuration; record the accepted response codes, timeout, retry schedule, cutoff, and replay options.
A terminal failure means the provider has reached a point where its policy no longer calls for automatic delivery attempts. You might still recover the event through manual replay, an API, or reconciliation, which means comparing your stored data with the provider’s current data. The term alone does not say whether recovery remains available.
The consequence depends on the provider. GitHub’s failed delivery requires manual action rather than automatic redelivery, while its documented manual replay option covers deliveries from the past three days. Shopify’s retry policy can also lead to deletion of an Admin API-configured subscription after repeated failures. In that case, recovery involves more than retrying one event: the receiver may need to recreate the subscription and identify data it missed.
When a delivery reaches terminal failure, alert on it. Then determine whether the subscription still exists and use the provider’s documented replay or data-recovery path. For important records, comparing the receiver’s stored data with the provider’s current data helps recover events that exhausted their retry policy.
A sender that retries after a timeout may deliver an event again, even if the receiver performed the work but its response never reached the sender. The receiver should therefore make repeated handling safe. Idempotent handling means processing the same event more than once produces the same final result as processing it once. A receiver can also store a stable event or delivery identifier and check that record before repeating work.
A successful delivery does not imply that events arrive in the order they occurred. Svix describes ordinary endpoints as having best-effort ordering and offers FIFO endpoints for strict ordering. FIFO, short for “first in, first out,” means the sender preserves message order, but a failed message can block later messages. That behavior belongs to Svix’s service and illustrates the trade-off; it does not establish a general rule for other providers. Check whether your provider guarantees order for your endpoint type and configuration before relying on arrival order.
A receiver should acknowledge an event only after it has accepted responsibility for it, such as by storing it durably or placing it in a durable queue. It should then process the event in a way that tolerates duplicates, monitor failed deliveries, and use manual replay or reconciliation when automatic retries end.
Do not assume that an HTTP success means downstream work completed, that every outage will be followed by eventual delivery, or that the sender will preserve event order. Verify the provider’s current documentation for response requirements, timeouts, automatic retry limits, terminal behavior, and replay access. Those details determine what the sender guarantees and what your receiver must handle.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com