Interactive example
Failure response lab
Compare three failure contracts. The one-click action creates the 429 scenario by default.
- Content-Type
- application/json
- X-Event-Id
- evt_invalid_400
{"event":"invoice.created","data":{"invoice_id":null}}Configured response
400 Bad Request
Content-Type: application/json
{"error":"invalid_payload","field":"data.invoice_id"}Synthetic data only. Never paste production tokens, signing secrets, personal data, or payment information into a public test endpoint.
One-click test setup
Creates a temporary bucket and applies this guide's expected response. You can inspect and delete it from the dashboard.
Use Controlled Failures Instead of Production Incidents
Happy-path webhook tests prove only that one request can receive a 2xx response. Real integrations must also handle invalid input, rate limits, and temporary receiver failures without losing events or creating duplicate side effects.
A temporary endpoint with a configurable response lets you repeat those failures on demand. Keep the sender code and synthetic event constant while changing only the HTTP response. That isolates retry policy from payload generation and network routing.
Choose the Correct Failure Contract
Use 400 when the receiver cannot process the request because the request itself is invalid. Repeating the same body usually cannot fix a missing identifier or malformed JSON. Use 429 when the receiver is rate limiting an otherwise valid request. Use 500 when the receiver encountered an unexpected condition and a later attempt may succeed.
The exact retry decision belongs to the sender’s documented contract. This lab is designed to reveal the current behavior, not to assume every provider treats a status identically.
| Response | Meaning in the test | Behavior to verify |
|---|---|---|
| 400 Bad Request | The synthetic request is invalid | Stop or quarantine until the payload changes |
| 429 Too Many Requests | The receiver is temporarily rate limiting | Wait, apply jitter, and retry within a bound |
| 500 Internal Server Error | The receiver failed unexpectedly | Retry transiently, then dead-letter or alert |
| 2xx after failures | The receiver recovered | Process once and stop scheduled retries |
Status codes communicate the HTTP outcome. Your application still needs a documented retry ceiling, idempotency key, and terminal failure path.
Run the 429 Scenario
The one-click action creates a bucket that responds with 429, an actual Retry-After: 30 response header, and a JSON body containing the same synthetic retry interval. Send an event with a stable X-Event-Id, record the time of every attempt, and observe whether the sender retries immediately or waits.
Open Response settings to change the header, body, or delay without changing the sender. A response delay is useful for testing timeout handling, while Retry-After shows whether a rate-limited sender honors explicit server guidance.
- Record attempt timestamps and calculate the real delay
- Confirm retries have a maximum attempt or time limit
- Use jitter so many failed events do not retry simultaneously
- Keep the same event ID across attempts so duplicates are detectable
Run the 400 and 500 Scenarios
For the 400 case, configure the response editor with the status and body shown in the lab, then send the invalid synthetic invoice. The sender should expose a useful terminal error or route the event for correction instead of repeatedly sending the same body.
For the 500 case, run enough attempts to observe the complete retry schedule. Then change the endpoint response to 200 without changing the event ID. Verify that the sender accepts recovery, cancels future retries, and causes the downstream business action at most once.
Verify Idempotency and Terminal Handling
Retries create at-least-once delivery. The receiver must claim a stable event identifier before performing a charge, message, fulfillment, or state transition. A database uniqueness constraint is stronger than an in-memory duplicate check when concurrent deliveries are possible.
Finish the test by checking the terminal path: dead-letter queue, failed execution record, alert, or manual review. A sender that silently stops after its last retry is difficult to operate even when its backoff timing is correct.
- Count captured attempts for one stable event ID
- Confirm successful recovery produces one business result
- Confirm the last failed attempt creates an actionable record
- Delete the temporary bucket when the retry experiment finishes
Common questions
Frequently asked questions
- Should a webhook sender retry a 400 response?
- Usually not without changing the request. A 400 response means the receiver considers the request invalid, so repeating the same payload can create a retry storm. Follow the specific provider contract.
- What should happen after a 429 response?
- A sender should respect the receiver’s retry guidance when provided, apply bounded backoff with jitter, and stop after a documented limit. Do not run many concurrent retries against a rate-limited service.
- Why test a 500 response more than once?
- A single attempt proves only error handling. Repeated controlled failures reveal the retry schedule, maximum attempts, duplicate-delivery behavior, logging quality, and final dead-letter or manual-review path.
Primary sources
Official references
Try it with a real request
Turn the guide into a test.
Open a temporary endpoint, send your payload, and inspect exactly what arrived.
Open the tester