Stripe webhook failed: diagnosis and fixes
A Stripe webhook can fail before it reaches your application, during signature verification, or after your handler begins processing the event.
Start with the delivery attempt in Stripe, then correlate it with the exact request received by your endpoint and the application log for the same event ID.
Symptoms
- Stripe shows a non-2xx response for the delivery attempt.
- Your endpoint returns HTTP 400 with a signature or payload error.
- Stripe reports a timeout even though the handler eventually finishes.
- The endpoint returns 200, but the expected payment or subscription state does not change.
- The same Stripe event is processed more than once after a retry.
Likely causes
| Failure point | Common cause | Evidence to inspect |
|---|---|---|
| Delivery | Wrong URL, unavailable host, TLS failure, or blocked network path. | Stripe delivery status and response details. |
| Signature verification | Wrong endpoint secret, parsed body, or expired timestamp. | Stripe-Signature header and raw request body. |
| Payload handling | Unexpected event type or unsafe field assumption. | Event type, API version, and parsing error. |
| Application processing | Database, queue, or downstream dependency failure. | Application trace correlated by Stripe event ID. |
| Retry handling | The handler is not idempotent. | Repeated event IDs and duplicate writes. |
How to diagnose a failed Stripe webhook
- Open the failed delivery attempt in Stripe and record the event ID, destination URL, response status, and response body.
- Find the matching request in Hookmetry by time, endpoint, or event ID.
- Confirm that Stripe-Signature is present and that the captured body matches the bytes used by your verifier.
- Separate signature failures from application failures. A valid signature does not prove that business processing succeeded.
- Trace the Stripe event ID through your application, queue, and database logs.
- Correct the failing stage, then send a new test event or use a controlled replay when signature freshness is not required.
Respond before doing slow work
Verify the request, persist the event or enqueue a job, and return a 2xx response promptly. Perform email, fulfillment, and other slow work outside the request-response path.
javascript
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET
);
if (await hasProcessed(event.id)) return res.sendStatus(200);
await enqueueStripeEvent(event);
res.sendStatus(200);
});Verify the fix
- A new Stripe delivery receives a 2xx response without timing out.
- Signature verification uses the raw body and the secret for the same endpoint and environment.
- The event ID appears once in the idempotency store.
- The expected application state change completes and is observable in application logs.