Stripe's docs are explicit: without signature verification, an attacker could send fake events to fulfil orders or grant access; endpoints may receive the same event more than once; and delivery order is not promised. (Source: Stripe docs, 2026-09 · verified 2026-09-21) A webhook (a message a provider such as Stripe sends to your server when something happens, like a payment) handler that passes the happy path has been tested against none of those.
The easy half
stripe listen --forward-to localhost:4242/webhook
stripe trigger payment_intent.succeeded
That confirms the endpoint receives and parses a real event. It is where most testing stops.
PY-01: signatures
Send the same payload three ways and expect 4xx with no state change from each:
- No Stripe-Signature header.
- A header signed with a different secret.
- A valid header with one byte of the body changed.
The third case is the one that catches the common failure: a framework that parses JSON before the handler sees it, so the raw body (the request bytes exactly as sent; signature checks fail if a framework parses them first) the signature was computed over no longer exists. The handler either fails every real event (noticed quickly) or was "fixed" by skipping verification (not noticed at all).
Add a fourth: a valid signature with a timestamp 10 minutes old. The official libraries reject it by default with a 5-minute tolerance; a handler that set tolerance to 0 to make a test pass has removed replay protection.
PY-02: duplicates and order
Deliver the same event id twice. Count the state changes; there must be one. The docs suggest logging processed event ids, and the test checks that the log is consulted.
Then deliver a related pair in reverse: invoice.paid before customer.subscription.created. The account must end in the state the written rules specify. A handler written from a happy-path example encodes the order the example showed.
PY-03 to PY-05: the state afterwards
Edit the client-side plan flag and call a gated endpoint; the server must refuse. Change the amount in the checkout request; the server must recompute or reject. Trigger cancel, downgrade, failed renewal and refund in test mode and read the entitlement after each.
Before switching to live keys
Run all of it against the staging endpoint with test keys, then switch keys (sk_live_, and a new webhook signing secret for the live endpoint) and run the negative cases once more against the live endpoint with test events. SC-04 checks that production is not still on sk_test_.
The Stripe hub lists the vendor defaults with dates; the payments category has the five items with criteria.