What is Webhook?
A message another service (like Stripe) sends to your server to say something happened, such as 'this customer paid'.
Where Webhook goes wrong in real apps
Each of these is a short scenario built from a real failure in an app made with AI. Read the answer here, or walk through it yourself in about four minutes.
Don't trust the browser · 4 minutes · Pro
The webhook anyone could send
Twelve accounts upgraded to Pro overnight. Stripe shows no payments from any of them.
Read the fixWhy must I verify Stripe webhook signatures?
Because your webhook URL is public and anyone can send it a fake 'checkout completed' event with their own user id. Stripe signs every event with your webhook signing secret. Verify the Stripe-Signature header with stripe.webhooks.constructEvent using the raw request body, reject anything that fails, and only then grant access or credits.
Getting paid · 4 minutes · Free
The success page anyone could visit
Your app has 41 Pro accounts. Stripe shows 29 paying customers. Nobody hacked anything: twelve people just typed an address.
Walk it, freeShould I grant access on the Stripe success page?
No. The success page is just where the browser lands after checkout, and anyone can type its address, so granting access there gives your paid plan away. Grant access in your webhook when Stripe sends checkout.session.completed, verified by its signature. Let the success page only read and display the plan your database already holds.
Money and secrets · 5 minutes · Pro
The webhook that arrived twice
A customer bought one month of credits. Their account shows two months. Your Stripe dashboard shows one payment.
Read the fixWhy did my Stripe webhook run twice?
Stripe delivers webhooks at least once and retries when your endpoint is slow or fails, so the same event id can arrive twice. Record each processed event id in a table with a unique key and skip repeats, and always verify the signature against the raw request body.
Getting paid · 4 minutes · Pro
The webhook that failed every time
Payments work. Upgrades don't. Stripe's dashboard shows every webhook to your app failing with a 400.
Read the fixWhy does Stripe webhook verification fail?
The signature covers the exact raw bytes Stripe sent. It fails when your code parses the body as JSON before verifying, or when it uses the wrong signing secret. In Next.js App Router, read the body with request.text() and pass it unchanged to constructEvent. Each endpoint, including the Stripe CLI, has its own whsec_ secret.
Money and secrets · 4 minutes · Pro
Cancelled on day 3
Two support emails. One customer cancelled in October and still has Pro in December. Another lost access the moment they clicked Cancel, with 27 paid days left.
Read the fixWhat happens when a subscription is cancelled?
Usually the subscription stays active until the end of the paid period, with cancel_at_period_end (or cancel_at) set, and is deleted afterwards. Keep access until then, and handle subscription updated and deleted webhooks so access follows Stripe's state, not just the first checkout.
Don't trust the browser · 4 minutes · Free
The $1 Pro plan
Your Pro plan costs $49 a year. Stripe shows three new Pro customers this morning who each paid $1.
Walk it, freeCan users change the price at checkout?
Yes, if your server takes the amount or price ID from the browser. Anything the browser sends can be edited, so a visitor can create a checkout at any price. Send only which plan the visitor wants, and have the server look up the allowed price from its own list before creating the Stripe checkout.
Related terms
- Stripe
- A popular payments service. It charges cards and tells your app what happened through webhooks.
- Request
- A message your browser (or any program) sends to a server asking for something: 'give me invoice 105' or 'save this profile'.
- Server
- A computer that runs your app's code out of sight. Users can't change what happens there, which is why checks belong there.
- Grant
- Permission for a type of user to use a table at all. Without it you get 'permission denied', before any row rules are checked.
- Endpoint
- One specific address on your server that does one job, like /api/invoices. Each one needs its own safety checks.
- Browser
- Chrome, Safari and friends. Everything that runs here is visible to, and changeable by, the person using it.