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

ProviderWants an answer withinIts own retriesAfter that
Stripe“Quickly”, before any real workUp to three days, with exponential backoffRetries stop. Manual resend, event by event, for 15 days in the Dashboard or 30 with the CLI.
Shopify5 secondsUp to 8 times in 4 hoursAfter repeated failures the webhook subscription itself is removed.
GitHub10 secondsNone — a failed delivery is not redelivered automaticallyManual redelivery, or a script of your own against the API.
Supabase database webhooksA short timeoutNone — fire-and-forgetThe 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

the path of one event
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 handlerWith Queuey in between
Answering in timeYour 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 failsThe 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 outA 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.
ResendingPer 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 eventEach 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 failureReading 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.
BurstsA 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 consumerOne 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.request is 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

  1. One queue per provider endpoint. The queue's ingress URL is what you register at the provider. It answers 202 by default; a provider that insists on exactly 200 gets that from the queue's Security tab.
  2. 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.
  3. 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.
  4. 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.
ProviderCheck at Queuey's ingressHeader you can forwardNote
StripeSigning template: the signature and a five-minute windowStripe-SignatureCarries a timestamp, so widen the handler's tolerance — see the Stripe guide.
ShopifyNone yet — keep the check in your handlerX-Shopify-Hmac-Sha256No timestamp in the signature; nothing to widen.
GitHubSource-IP allowlist — GitHub publishes its hook addressesX-Hub-Signature-256No timestamp in the signature; nothing to widen.
Without a signing template, the queue accepts unsigned requests
Anyone who knows the ingress URL can then post to it. A forged event still stops at your handler, whose check of the forwarded signature rejects it, and ends in the dead-letter queue instead of in your system — but it has been stored and counted first. Use the source-IP allowlist where the provider gives you one.

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:

Related