Stripe in an AI-Built App: Five Payment Bugs to Fix

AI-built apps often mark orders as paid from the browser. The five Stripe bugs that cost founders money, how to spot each one, and what Stripe’s docs say to do.

On this page · 6 sections
  1. 1. Access is granted on the success page
  2. 2. The webhook doesn’t check the signature
  3. 3. The same payment is processed twice
  4. 4. The price comes from the browser
  5. 5. Only the first payment is handled
  6. What should you check before going live?

Key takeaways

  • The most common payment bug in AI-built apps is granting access when the browser reaches the success page. Stripe’s own docs say you can’t rely on that page.
  • Fulfill orders from a verified webhook, make fulfillment safe to run twice, and take prices from your server, never from the browser.
  • Each bug below can be spotted in a few minutes by reading one function or making one test purchase.

Adding Stripe to an AI-built app looks easy: the AI tool generates a checkout button, you make a test payment, a success page appears. The trouble is what happens behind that page. In many prototypes, the app decides a customer has paid because the browser said so, and anyone who can edit a request in the browser can say so too.

These are the five payment bugs worth fixing before you take real money, each with what Stripe’s documentation recommends. The post is part of the guide to making an AI-built prototype ready for real users.

1. Access is granted on the success page

The app shows a “Thanks for your purchase” page after checkout and, on that page, marks the order as paid or unlocks the subscription. Two things go wrong: customers who pay and close the tab never get what they paid for, and anyone who finds the success URL can visit it without paying.

Stripe is direct about it: “You can’t rely on triggering fulfillment only from your checkout landing page, because it’s not guaranteed customers visit that page” (Stripe). The fix is a webhook: Stripe calls your server with a checkout.session.completed event, and your server grants access. The success page can then show the result, but it’s no longer what decides it.

How to spot it: search the code for where an order or subscription becomes “paid” or “active”. If it happens in a page component or after a redirect, it’s this bug.

2. The webhook doesn’t check the signature

Once the webhook exists, anyone can send requests to its URL. Stripe signs every event with a secret only you and Stripe know, and warns that “without verification, an attacker could send fake webhook events to your endpoint to trigger actions like fulfilling orders, granting account access, or modifying records” (Stripe).

Verification is one function call in Stripe’s libraries, using the raw request body, the Stripe-Signature header and your whsec_… signing secret. A common AI-generated failure is parsing the body as JSON first, which changes it and makes verification fail; the “fix” is then to remove the check.

How to spot it: open the webhook handler. If it reads event.type without calling a verification function first, anyone can fake a payment.

3. The same payment is processed twice

Stripe retries events that don’t get a quick success response, for up to three days in live mode, and says endpoints “might occasionally receive the same event more than once” (Stripe). If your handler adds credits, sends a license key or creates an order every time it runs, some customers get them twice.

Stripe’s guidance is to log the event IDs you’ve processed and skip ones you’ve seen, and to make fulfillment “safe to run multiple times, even concurrently” (Stripe). In practice: store the Checkout Session ID with the order and use a unique constraint so a second insert fails.

How to spot it: replay an event from the Stripe dashboard. If the customer gets two of something, it’s this bug.

4. The price comes from the browser

The checkout button sends the amount to your server, or straight to Stripe from the frontend. Anyone can change 4900 to 100 in the request and buy for a dollar.

The fix is to create prices in Stripe (or keep them on your server) and have the browser send only which product and how many. Your server looks up the price and creates the Checkout Session. The same applies to discounts, plan names and quantities: the browser asks, the server decides.

How to spot it: look at the request the checkout button sends. If it contains an amount or a price, it’s this bug.

5. Only the first payment is handled

Prototypes usually handle one event: the first successful checkout. Real payments have a life after that. Bank transfers and other delayed methods complete later, and Stripe sends checkout.session.async_payment_succeeded or …_failed when they do (Stripe). Subscriptions renew, fail, get cancelled and get disputed, each with its own event.

If the app only listens to the first event, customers whose card fails next month keep access forever, and customers who paid by bank transfer never get it. List the events your business depends on and handle each one, and don’t assume they arrive in order: Stripe doesn’t guarantee event ordering.

How to spot it: in the Stripe dashboard, check which event types your webhook endpoint subscribes to. If it’s only checkout.session.completed and you sell subscriptions, it’s this bug.

What should you check before going live?

  • Use separate test and live keys, stored on the server only. The secret key (sk_live_…) never goes in the frontend; see secret keys in your frontend.
  • Register the live webhook endpoint separately: test and live endpoints have different signing secrets.
  • Make one real purchase with a real card, refund it, and check that access was granted and then handled correctly.

Frequently asked questions

Do I need Stripe webhooks if I use Stripe Checkout?

Yes. Stripe says you can’t rely on the success page alone for fulfillment, because customers may never reach it. Webhooks are required if you sell subscriptions or accept payment methods that confirm later, such as bank transfers.

How do I stop fake Stripe webhook requests?

Verify the signature on every event with Stripe’s library, using the raw request body, the Stripe-Signature header and your endpoint’s signing secret. Reject any event that fails verification before doing anything with it.

Why did my customer get charged once but receive two orders?

Stripe can deliver the same event more than once, and retries events that time out. Record each Checkout Session or event ID when you fulfill it, and skip any ID you’ve already processed.

Sources

  1. Stripe — Fulfill orders
  2. Stripe — Receive Stripe events in your webhook endpoint
AIPaymentsStripeWebhooks

Filipe Eduardo

Senior Software Engineer. Seven years building web and mobile products end to end and leading the teams that ship them.

Follow along

Want to talk
about this?

I work on problems like this every day. Tell me about yours.

Get in touch