Stripe · Webhooks · Idempotency

Stripe webhook testing: make each payment grant access exactly once

By Su, offensive security engineer (OSCP, CISSP) · Updated

A Stripe webhook handler is correct when each payment changes your customer's access exactly once, even if Stripe retries the event, sends events out of order, or someone posts a forged event to your endpoint. AI-generated handlers often work in a happy-path demo but skip signature verification or de-duplication, so a retried checkout.session.completed can grant the paid plan twice and an unsigned request can grant it for free.

The four tests that matter

  1. Duplicate: send the same event twice. Access must change once.
  2. Out of order: deliver a later event before an earlier one. The final state must be right.
  3. Concurrent: deliver the same event twice at the same moment. Two parallel requests must not both apply it.
  4. Forged: send an event with a missing or wrong signature. It must be rejected and grant nothing.

What a safe handler does

How to test it yourself

In Stripe test mode, forward events to your local or staging endpoint with the Stripe CLI, then trigger and resend them:

stripe listen --forward-to localhost:3000/api/stripe/webhook
stripe trigger checkout.session.completed
stripe events resend evt_XXXXXXXX   # replays the same event

Then check your database: one payment, one change in access. For the forged case, post the same JSON with a wrong or missing signature header and confirm you get a 400.

Want it fixed and proven?

The Launch Pass includes the Stripe webhook fix, proven with duplicate, concurrent, out-of-order and unsigned replays in test mode. The replay results go in your proof report, so you can show a customer or investor that each payment grants access exactly once.

Questions

Why does Stripe send the same webhook event twice?

Stripe retries an event if your endpoint does not return a success status quickly enough, and it can deliver an event more than once. Your handler has to treat the event ID as the key and ignore repeats.

Do I need to verify the Stripe webhook signature?

Yes. Without verification anyone who finds your webhook URL can post a fake payment-succeeded event and unlock a paid plan for free. Verify against the raw request body with your endpoint's signing secret.

Do you test with real payments?

No. All Stripe testing is done in test mode on a staging copy of your app, with no real charges and no production secrets.

Request Launch Pass See pricing

Related