Stripe relies entirely on asynchronous webhooks to inform your application about payment states (e.g., payment_intent.succeeded). Because payments are critical path, Stripe enforces strict delivery and validation requirements. If you do not understand their retry mechanics or HMAC signature scheme, your system will drop payments or succumb to replay attacks.
When verifying Stripe signatures in Node.js, the stripe.webhooks.constructEvent() function requires the exact raw bytes of the HTTP request body. If you use express.json() before verifying the webhook, Express mutates the raw stream into a parsed object. The signature verification will instantly fail.
The Fix: Use express.raw()
app.post('/webhook/stripe', express.raw({type: 'application/json'}), (req, res) => { ... });Stripe expects your endpoint to return an HTTP 200 OK within a few seconds. If your database write is slow, or your server crashes, Stripe will exponentially retry the webhook for up to 3 days. This creates a severe risk of duplicate processing.
event.id in your database. Before processing a payment, verify you haven't seen this event ID before.Stripe includes a timestamp in their Stripe-Signature header. By default, the SDK rejects events older than 5 minutes. This prevents attackers from capturing a valid webhook (e.g., "Grant premium access") and replaying it hours later.
Gotcha: If your server clock is heavily skewed, or if you are using a slow proxy, legitimate Stripe events will be rejected with a Tolerance error.
Was this page helpful?
Your feedback helps us improve the docs.