How-to guide
Do you need a queue in front of your webhook handler?
Stripe, Shopify, GitHub, a partner's system — whoever sends you webhooks has already decided how long to wait for your answer, how often to try again, and when to give up. Your handler has to live up to the least patient of them, every hour of every day, including the afternoon of a bad deploy. A queue in between changes whose patience matters: the provider talks to an ingress built to accept and store, and everything after that runs on your terms. This page is the honest version of when that is worth it — and when it is not.
What providers do when your handler fails
| Provider | Wants an answer within | Its own retries | After that |
|---|---|---|---|
| Stripe | “Quickly”, before any real work | Up to three days, with exponential backoff | Retries stop. Manual resend, event by event, for 15 days in the Dashboard or 30 with the CLI. |
| Shopify | 5 seconds | Up to 8 times in 4 hours | After repeated failures the webhook subscription itself is removed. |
| GitHub | 10 seconds | None — a failed delivery is not redelivered automatically | Manual redelivery, or a script of your own against the API. |
| Supabase database webhooks | A short timeout | None — fire-and-forget | The row change is gone. |
From each provider's own documentation, September 2026.
Two things follow. The availability your handler really needs is set by the strictest row that applies to you. And none of the rows is about your side: nobody tells you that events are failing now, nobody keeps the ones that ran out of attempts, and nobody can show you what your code answered.
The retries that do exist are still worth having, and a queue in between takes none of them away. They now cover the hop from the provider into Queuey — the shortest and simplest hop there is — instead of being spent on your handler's bad afternoon. If Queuey cannot accept an event, the provider retries exactly as it would have against you.
What changes with Queuey in between
provider ──POST──▶ Queuey ingress ──▶ your queue ──POST──▶ your handler
retries on its accepts, stores, retried, held, answers on your terms;
own schedule answers 2xx dead-lettered, knows Queuey by an
(if it retries) replayable API key| Provider straight to your handler | With Queuey in between | |
|---|---|---|
| Answering in time | Your handler has to answer within the provider's limit — seconds — before it does any real work. | Queuey returns the 2xx once the event is stored. Your handler gets the time its work needs, and can be slow, mid-deploy or down. |
| When the handler fails | The provider's schedule decides: days, hours, or no retry at all. When it runs out the event is gone — or the subscription is. | Retries follow the kind of failure: a handler that is down is held and probed instead of burning attempts, and what cannot be delivered lands in a dead-letter queue. Nothing expires before your retention window does. |
| Finding out | A failure email from the provider, a dashboard nobody watches, or a customer — often after the retries have run out. | An issue is raised when deliveries start failing, with an alert by email or Slack — while the events are still waiting in the queue. |
| Resending | Per provider, per event, in each provider's own dashboard, inside that provider's window. | Replay one event, or a filtered set in bulk, once the cause is fixed — in one console, for every provider you receive from. |
| Finding an event | Each provider's delivery log for what was sent; your own logs for what your code did with it. | Search and filter by status, event type, customer key and time. Every attempt shows what the handler answered and the decision it led to. |
| Understanding a failure | Reading logs. | The assistant in the console reads the attempts with you: a diagnosis, a suggested fix, and a resolve plan that runs only when you confirm it. |
| Bursts | A sale, a renewal run or a bulk import arrives as a burst, straight at your handler. | The burst lands in the queue. Deliveries go out at the concurrency you set. |
| More than one consumer | One endpoint per consumer at every provider, each with its own secret and its own failure handling. | One endpoint at the provider. The queue fans out to several targets, each with its own auth and retries. |
When it is not worth adding — and when it cannot work
- Your endpoint already does nothing but verify, write to a durable queue you operate, and return
2xx— and you are content with the dead-letter handling, replay and visibility you have there. Queuey would replace that plumbing; it would not close a gap. - The events are low-stakes, the handler is simple and idempotent, and the provider's own retries plus an occasional manual resend are enough.
- The provider waits for your answer. Stripe's
issuing_authorization.requestis a question, and the response is the decision; a queue answers before your code has run. - The provider verifies the endpoint with a challenge that the endpoint must echo back, as Slack's Events API does. Queuey answers with its own receipt, so that verification fails.
What it costs. One more hop and one more vendor in the event path. The provider's retries still stand behind Queuey's ingress, exactly as they stood behind your endpoint.
Setting it up
- One queue per provider endpoint. The queue's ingress URL is what you register at the provider. It answers
202by default; a provider that insists on exactly200gets that from the queue's Security tab. - Let Queuey check the sender where it can — a signing template where Queuey has the provider's scheme, a source-IP allowlist where the provider publishes its addresses, an API key where the provider lets you add a header.
- Deliver to your handler with an API key — or a bearer token, OAuth2, or signed deliveries. That is how your handler knows the caller is Queuey, on the first attempt and on a replay next week.
- Optional: keep your handler's own signature check. Map the provider's signature header through as a mapped header. Queuey delivers the body byte for byte — as long as the payload format stays Raw and the queue has no payload mutations — so a signature over the raw body still matches.
| Provider | Check at Queuey's ingress | Header you can forward | Note |
|---|---|---|---|
| Stripe | Signing template: the signature and a five-minute window | Stripe-Signature | Carries a timestamp, so widen the handler's tolerance — see the Stripe guide. |
| Shopify | None yet — keep the check in your handler | X-Shopify-Hmac-Sha256 | No timestamp in the signature; nothing to widen. |
| GitHub | Source-IP allowlist — GitHub publishes its hook addresses | X-Hub-Signature-256 | No timestamp in the signature; nothing to widen. |
The Stripe guide is the worked example: every console field, the handler code in three languages, and how to move an endpoint that is already in production.
Where this sits: operational event delivery
Provider webhooks are one corner of a larger job: getting an event from the system where it happened to the system that must act on it, and operating that delivery — knowing whether it arrived, deciding what to do when it did not, and recovering without losing or duplicating work. The same engine covers the other corners:
- webhooks you send to your own customers, where you are the provider in the table above — quickstart, customer context, per-customer ordering;
- one event that several systems must act on — fanout;
- event streams you distribute to partners — WaaS producer API;
- database changes as events — Supabase database webhooks;
- producers on networks that come and go — Queuey Edge.
Related
- Stripe webhooks — the worked example, end to end.
- Reliable delivery — retries by failure class, hold-on-timeout and the dead-letter queue.
- Operational runbook — diagnose, resume and replay when a handler has been down.