Blog · · 6 min read
Webhook Response Status Codes: What to Return and What Senders Do
Which status code a webhook receiver should return, and how Stripe, Shopify, GitHub, Slack, LINE and Standard Webhooks treat non-2xx, redirects, 410 and 429.
Key points
- Return 2xx when you've received the event, 400 or 401 when the request is invalid (for example a bad signature), and 5xx when you want the sender to try again later. 200 and 204 are treated the same.
- The major senders count only 2xx as success and treat redirects as failures instead of following them.
- Whether 410 disables an endpoint and whether 429 slows the sender down varies by provider, so check before relying on either.
A webhook receiver really only needs three answers. Return 2xx when you've received the event. Return 400 or 401 when the request itself is invalid, such as a signature that doesn't match. Return 5xx when something on your side failed temporarily and you want the sender to try again. Most senders count only 2xx as delivered and retry everything else. This post covers what to return in each situation and how the major providers handle each class of response.
What the receiver should return
| Situation | Return | Typical sender behavior |
|---|---|---|
| Received and recorded (work still pending) | 200 or 204 | Success, no retry |
| The same event again (duplicate) | 200 or 204 | Success, no retry |
| Bad signature or missing headers | 400 or 401 | Failure; most senders retry |
| Unknown event type | 200 or 204 | Success; accept and ignore |
| Temporary failure (database down) | 500 or 503 | Failure; retried |
| Processing took longer than the timeout | (no response) | Failure; retried |
| This URL should no longer receive events | 410 | Depends on the sender |
Three rows people get wrong:
- Return 2xx for duplicates. A 4xx tells the sender the delivery failed, and it will keep retrying.
- Return 2xx for event types you don't handle. Otherwise every new event type the sender adds becomes a stream of failures on your side.
- Most senders retry 4xx too. That's fine: once you fix a wrong secret, the next retry verifies.
200 vs 204
No difference. Stripe's docs describe a 200 as a successful delivery and talk about success as 2xx; Shopify treats any 200-series response as successful; GitHub asks for a 2XX within 10 seconds (all checked on 2026-09-27). An empty 204 and a 200 with a short body are equivalent.
Senders often store the response body in their delivery log, so a short reason on errors helps whoever reads that log. Don't return personal data or stack traces.
How the major senders handle responses
Checked against each provider's official documentation on 2026-09-27.
| Sender | Success | 3xx | Non-2xx | Timeout |
|---|---|---|---|---|
| Stripe | 2xx | Failure | Retries with exponential backoff for up to 3 days (live) | Not stated |
| Shopify | 200-series | Not stated | Up to 8 retries over 4 hours, then the subscription is removed | 5 s (1 s to connect) |
| GitHub | 2XX | Failure (anything other than 2XX) | No automatic retries; redeliver manually or via API | 10 s |
| Slack (Events API) | 2xx | More than 2 redirects is a failure | Retries immediately, after 1 min and after 5 min; x-slack-no-retry: 1 opts out |
3 s |
| LINE | 200-series | Not stated | Redelivery only if you enable it (attempt count not disclosed) | 2 s (request_timeout) |
| Standard Webhooks (spec) | 2xx | Failure; update the URL instead | 410: disable the endpoint. 429, 502, 504: throttle | Not stated |
Sources: Stripe, Shopify troubleshooting, Shopify HTTPS delivery, GitHub failed deliveries, GitHub best practices, Slack Events API, LINE receiving, LINE error statistics, Standard Webhooks spec
A few details from those docs (timeouts are compared in Webhook timeouts):
- Slack disables event delivery for apps that fail too often. If more than 95% of delivery attempts within 60 minutes hit a failure condition (certificate problems, responses slower than 3 seconds, more than 2 redirects, non-2xx), Slack turns off event subscriptions.
- Only Slack lets the receiver say "don't retry." Add
x-slack-no-retry: 1to a non-2xx response. - Shopify deletes the subscription. Subscriptions created through the Admin API are removed after 8 consecutive failures.
4xx vs 5xx
Many senders retry both, but returning the right class still helps: whoever reads the sender's delivery log can tell the two cases apart.
- 4xx: the request is the problem (bad signature, malformed body). Sending it again unchanged will probably fail again.
- 5xx: your side couldn't process it right now (database outage, dependency down). A later attempt will probably work.
If signature verification fails, don't process anything and return 400 or 401. If verification passes but processing fails before you've enqueued the work, return 5xx so the sender retries. Once the work is safely in your own queue, return 2xx and let your queue handle retries.
429 and Retry-After
Returning 429 with Retry-After when you're overloaded rarely helps. None of the senders in the table above documents honoring Retry-After, and the Standard Webhooks spec only recommends throttling. The dependable way to handle load is to enqueue immediately and return 2xx.
Redirects, and the Google Apps Script 302
Senders generally don't follow redirects. Stripe treats a 3xx as a failure and tells you to register the URL the redirect resolves to. The reason is simple: following a redirect would send the payload and signature somewhere the sender never validated. The Standard Webhooks spec also treats 3xx as a failure.
If you receive webhooks in a Google Apps Script web app, senders may see a 302. Apps Script redirects Content service output to a one-time URL on script.googleusercontent.com for security (Apps Script docs, checked on 2026-09-27). Your doPost runs, but a sender that doesn't follow redirects records a failure and retries. If you stay on Apps Script, make the handler safe to run twice, or put a receiver in front that can return 2xx directly.
The same thing happens with other receivers, for example GitHub webhooks to Jenkins returning 302. If a sender's log shows a 3xx, change the registered URL to the final destination.
What to decide if you're the sender
If your product sends webhooks, decide these four and put them in your receiver documentation:
- Which codes count as success (2xx only is the norm)
- Whether you follow redirects (usually not)
- Whether 410 disables an endpoint or is retried like any other failure
- Whether you honor 429 and
Retry-After
Retry schedules are covered in designing webhook retries, and receiver timeouts in webhook timeouts.
How Webhook Admin handles responses
As of 2026-09-27:
- Only 2xx is success.
- Redirects are not followed; a 3xx is a failure and is retried.
- 410 is retried like any other failure. It doesn't disable the endpoint by itself; endpoints are disabled after five days of continuous failures.
- 429 follows the normal retry schedule (immediately, 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h) and
Retry-Afteris not read. Instead, each endpoint gets at most 10 concurrent deliveries, and after 5 consecutive failures Webhook Admin pauses that endpoint for 30 seconds before trying one delivery. - Responses time out after 15 seconds, and the first 1,024 bytes of the response body go into the delivery log.
The API's own error codes are listed under errors. If you want a sender that records every status code and response body where your customers can see them, you can try Webhook Admin for free (50,000 messages a month): https://app.webhookadmin.com/signup
FAQ
Should a webhook endpoint return 200 or 204?
Either. The major senders treat the whole 2xx range as success and don't distinguish between 200 and 204. If you include a body, keep it short, because senders often store it in their delivery log.
Does returning 410 Gone stop webhook deliveries?
It depends on the sender. The Standard Webhooks spec recommends that senders disable an endpoint that returns 410, but many senders treat every non-2xx response the same and keep retrying. To stop deliveries for certain, delete or disable the endpoint in the sender's dashboard or API.
Why does my Google Apps Script webhook return 302?
Content returned by Apps Script's Content service is redirected to a one-time URL on script.googleusercontent.com for security. Most webhook senders don't follow redirects and record the 302 as a failure, even though your doPost already ran.