Skip to content
SSShipSafeStack

Checklist

Stripe webhook verification checklist

What to confirm so a Stripe payment is recorded from Stripe’s signed event, not from a success page the browser can fake.

Updated 16 Oct 2026

This is a practical checklist, not a guarantee. It is not a penetration test, a compliance audit, or proof that an app is safe to launch.

A success redirect only means the browser arrived somewhere. The record that a customer paid should come from Stripe, to a URL on your server, with the signature checked. Stripe’s current instructions are in their webhook signature docs.

The endpoint

  • Create the webhook in the Stripe dashboard, or with the Stripe CLI, for the events you actually handle. checkout.session.completed is the usual one for Checkout. Add the events your flow needs, and ignore the rest.
  • Point it at a server route, not at a page component that runs in the browser.
  • Store the signing secret (whsec_…) in the server environment. It is a secret. It does not belong in NEXT_PUBLIC_ or VITE_.

The check

  • Read the raw request body. Many frameworks parse JSON before your code runs. Signature verification needs the exact bytes Stripe sent. If verification always fails, this is the first place to look.
  • Pass that raw body and the Stripe-Signature header to the official Stripe library’s construct-event helper, together with the webhook secret. If it throws, return a 400 and do nothing else.
  • Do not trust a payment_intent or amount that the client posts to you alongside the redirect.

After a valid event

  • Decide what “paid” means in your database from the event, on the server.
  • Make the handler safe to run twice. Stripe retries. Use the event id so a second delivery does not grant the same purchase twice.
  • Return a 2xx only after you have accepted the event. A 500 tells Stripe to retry, which is what you want if your database write failed.

Try it before launch

  • Use the Stripe CLI or a test-mode endpoint to send a signed event, and confirm a bad signature is rejected.
  • Place a test-mode payment and confirm the database changes only after the event, not merely after the success URL loads.
  • Repeat with a test card that fails. The success page should not unlock the product.
Not a guarantee. This does not cover disputes, tax, or your own entitlement bugs. It is a checklist for the signature step, not a payments integration guide.