Stripe webhooks: verification and idempotency
Payment providers will retry webhooks; events can arrive out of order. Your handler must be idempotent and verify authenticity before mutating money-moving state.
Signature verification
Always use the SDK’s constructEvent (or equivalent) with the raw request body and the Stripe-Signature header. JSON parsing first breaks verification.
Idempotency table
Store event.id in a processed_webhook_events table with a unique constraint. Wrap business logic in a transaction:
- Insert
event.id(or noop if duplicate). - If insert succeeded, apply domain changes (extend subscription, mark invoice paid).
- Commit.
Duplicate delivery returns 200 quickly without double-shipping goods.
Return fast
Offload heavy work to a queue if processing exceeds provider timeouts; acknowledge with 200 only after you persist the event ID and enqueue work—or use at-least-once semantics your queue understands.
Testing
Use Stripe CLI stripe listen --forward-to localhost:3000/api/webhooks and fixture JSON for unit tests of your handler’s branching.
Summary
Treat webhooks as untrusted, duplicated, reordered inputs. Cryptographic verification + idempotent persistence is the minimum bar before touching ledgers or entitlements.