Why AI-Built Apps Break at Payments and Authentication, and How to Integrate Subscriptions Safely
By Obaid Tariq, Founder & Lead Engineer · Published
Disclosure: AI Dev Planner builds and repairs SaaS billing and authentication integrations for clients. The steps below are the ones we use, and none of them require hiring us. See our disclosure policy.
The short answer
AI-built apps tend to break at payments and authentication because both are distributed, stateful systems where the happy path is easy and the failure paths are the product. A checkout redirect is not proof of payment, a webhook can arrive twice or out of order, and a login that works in the browser can still let one customer read another's data. Integrate subscriptions safely by treating the payment provider as the source of truth, verifying and de-duplicating every webhook, granting access from stored subscription state, enforcing authorisation on the server and testing renewals, failures and cancellations before launch.
Why do AI-built applications break when you add payments or authentication?
Because payments and authentication are where an app stops being a set of screens and becomes a distributed system. Payment results arrive asynchronously from another company's servers, may arrive twice or out of order, and must never be trusted from the browser; authentication and authorisation must be enforced on the server for every request. AI tools are good at the visible happy path and routinely miss these invisible rules unless someone specifies and tests them.
The six failure modes we see most often
| Failure | What it looks like | Fix |
|---|---|---|
| Access granted on the success page | Users who close the tab after paying never get access; anyone who finds the success URL gets it free | Grant access from a verified webhook and stored subscription state, never from the redirect alone |
| Unverified webhook endpoint | Anyone can post a fake "payment succeeded" event and unlock paid features | Verify the provider's signature on the raw request body before doing anything |
| Duplicate or out-of-order events | Double credits, duplicate emails, or a cancelled customer reactivated by a late event | Record processed event IDs and fetch current state from the provider instead of replaying events in order |
| Authorisation in the browser only | Hiding a button is the only thing stopping a user calling the API for someone else's data | Check ownership and role on the server for every read and write |
| Session mistakes | Logout that does not really log out; sessions that survive a password change | Invalidate sessions server-side, renew session IDs on login and use secure cookie attributes |
| Test-mode assumptions in production | Live keys missing, webhooks pointing at a preview URL, or test and live data mixed | Separate keys, endpoints and databases per environment, with a pre-launch checklist |
Why the checkout redirect is not proof of payment
The most common pattern in AI-generated billing code is to unlock the product when the customer lands back on a "thank you" page. Stripe's own fulfilment guide warns against exactly this. Customers are not guaranteed to reach the landing page at all (a dropped connection after paying is enough), so fulfilment triggered only from that page will sometimes never run. It adds that webhook-based fulfilment is required if you sell subscriptions, because a subscription's state keeps changing after checkout: renewals, failures, upgrades and cancellations all happen when the customer is nowhere near your site.
Why webhooks are harder than they look
Stripe's webhook documentation sets out the rules an integration has to cope with: an endpoint may receive the same event more than once, events are not guaranteed to arrive in the order they were generated, and undelivered live-mode events are retried for up to three days with exponential back-off. An AI-generated handler that processes each event as it arrives, synchronously, without a record of what it has already processed, works perfectly in a demo and fails quietly in production. The fixes are in the next section.
Why authentication "works" but data still leaks
Authentication proves who someone is; authorisation decides what they may do. AI builders usually wire up authentication correctly through a provider, then leave authorisation to the front end or to a database policy that was generated once and never tested. Broken access control is the first category in the OWASP Top 10 (2021), and its examples include viewing someone else's account by changing an identifier and APIs with missing access control on POST, PUT and DELETE. If your app uses Supabase or another database exposed directly to the browser, read our explanation of row-level security, and for organisations and roles see multi-tenant SaaS architecture.
How do you integrate subscriptions, payment gateways and webhooks safely?
Let the payment provider hold card data and act as the source of truth, use its hosted checkout and customer portal where you can, and drive access from verified webhooks. Verify every webhook signature against the raw body, return a 2xx quickly and process the event in a background job, skip events you have already processed, and fetch current subscription state rather than trusting event order. Then test renewals, failed payments, upgrades and cancellations with simulated time before launch.
Step 1: Choose the integration that keeps card data off your servers
Use the provider's hosted checkout or embedded payment elements, so card numbers go straight to the provider. Stripe's integration security guide says that handling card data directly might require meeting more than 300 PCI DSS security controls, while its low-risk integrations send payment details directly to Stripe and reduce your obligations. Decide early whether you want a payment processor such as Stripe, where you handle sales tax and invoicing obligations yourself, or a merchant of record, which takes on more of that for a higher fee; our SaaS tech stack guide compares the two.
Step 2: Model subscription state in your own database
Store a small, explicit record per customer or organisation: the provider's customer ID, subscription ID, plan, status (for example trialing, active, past due, cancelled) and the end of the current period. Your app grants features from this record, which we call entitlements, never from a flag set by the browser. Keep the mapping from plan to features in one place in your code, so a pricing change is a one-line edit rather than a search through the codebase.
Step 3: Build a webhook handler that survives the real world
- Verify the signature on the raw request body. Stripe signs each event in the
Stripe-Signatureheader and requires the unmodified raw body for verification; frameworks that parse JSON first break this. Keep the library's default timestamp tolerance (five minutes in Stripe's libraries) to block replayed events, and never set it to zero. - Return a 2xx quickly, then do the work in a background job or queue. Stripe says to return a successful status before any complex logic that could time out, and recommends asynchronous processing so renewal spikes do not overwhelm your servers.
- De-duplicate. Store each processed event ID and skip repeats. Make the fulfilment logic itself safe to run twice: Stripe notes that its fulfilment function might be called multiple times, possibly concurrently, for the same checkout session.
- Do not depend on order. Stripe advises against using an event's timestamp to decide order. When an event arrives, fetch the current subscription from the API and write that state, rather than applying events as a sequence of changes.
- Listen only to the events you need, typically checkout completion, invoice paid, invoice payment failed, and subscription updated and deleted.
- Rotate the signing secret periodically and whenever it may have been exposed; Stripe lets you keep the old secret active for up to 24 hours while you switch.
Step 4: Make outgoing payment calls idempotent
When your server creates or updates something at the payment provider, a network timeout leaves you unsure whether it happened. Send an idempotency key with every create or update request. Stripe's API documentation explains that a retried request with the same key does not perform the operation twice; it suggests random V4 UUIDs, warns against using personal data such as email addresses as keys, and notes that for its v1 API, saved keys can be pruned after 24 hours.
Step 5: Let the provider's portal handle self-service
Card updates, invoice history, plan changes and cancellation are standard screens. Stripe's customer portal provides them, so your engineering effort goes into reacting to the resulting webhooks correctly. What you do when a renewal fails, how long you allow before restricting access and what emails you send, is covered in our guide to failed payments and dunning.
Step 6: Secure the authentication around billing
- Use a managed authentication provider or a well-maintained framework library rather than building login yourself. See what not to build in a SaaS.
- Follow OWASP's session guidance: renew the session ID after login or any privilege change, set
Secure,HttpOnlyandSameSiteon session cookies, and invalidate sessions on the server at logout. - Restrict who can change billing. In a B2B product, only an organisation's owner or billing admin should be able to change plan or payment method, and that check belongs on the server.
- Log billing and permission changes (who, what, when) without logging card details or secrets.
Step 7: Test the lifecycle before launch, not after
| Scenario | What should happen | How to test |
|---|---|---|
| Customer pays and closes the tab immediately | Access is still granted from the webhook | Complete a test checkout and close the window before redirect |
| Same webhook delivered twice | Processed once; no duplicate credit or email | Resend the event from the provider's dashboard or CLI |
| Renewal succeeds | Period end moves forward; access continues | Advance a simulated subscription past its renewal date |
| Renewal fails | Status becomes past due; dunning starts; access follows your policy | Use a test card that declines on renewal, with simulated time |
| Upgrade or downgrade mid-cycle | Features and proration match the new plan | Change plan in the customer portal during a test cycle |
| Cancellation at period end | Access continues until the period ends, then stops | Cancel, then advance simulated time past the period end |
| Forged webhook | Rejected with a 4xx; nothing changes | Post an unsigned or altered payload to the endpoint |
Stripe's test clocks (its billing simulations) exist for exactly this: they advance time so you can see how subscriptions, trials and renewal failures behave and confirm that your integration processes the billing lifecycle webhooks correctly. Add a nightly reconciliation job that compares your stored subscription states with the provider's, so a missed event is caught within a day rather than discovered by a customer. Our SaaS launch checklist covers the rest of what to check before going live.
Frequently asked questions
Can I rely on the Stripe checkout success page to unlock access?
Not on its own. Stripe's fulfilment guide says you cannot rely on the landing page because customers are not guaranteed to reach it, and that webhooks are required for subscriptions. You can trigger fulfilment from the landing page as well, for speed, as long as the same idempotent function also runs from the webhook.
What is webhook idempotency?
It means processing the same event twice has the same effect as processing it once. Store the ID of every event you have handled and skip repeats, and write fulfilment logic that checks whether the work has already been done before doing it.
How do I stop someone faking a payment webhook?
Verify the signature the provider attaches to every event, using the endpoint's signing secret and the raw, unparsed request body, and reject anything that fails. Keep the timestamp tolerance switched on to block replayed events, and keep the signing secret out of your code.
Should I store subscription status in my own database?
Yes. Store the provider's IDs, the plan, the status and the current period end, and grant features from that record. Treat the provider as the source of truth and update your record from verified webhooks, plus a regular reconciliation job that catches anything missed.
Do I need PCI compliance for a SaaS that uses Stripe?
You still have obligations, but using hosted checkout or embedded payment elements keeps card data off your servers and greatly reduces them. Stripe's integration security guide explains which integrations are lower risk; confirm your exact obligations with your provider.
Sources
- Stripe Docs: Receive Stripe events in your webhook endpoint — Signature verification on the raw body; default five-minute timestamp tolerance; duplicate deliveries; no guaranteed ordering; live-mode retries for up to three days; return 2xx quickly; asynchronous processing; rolling signing secrets with up to 24 hours of overlap. Accessed 20 October 2026.
- Stripe Docs: Fulfill orders (Checkout) — Do not rely on the landing page alone for fulfilment; webhooks are required for subscriptions; the fulfilment function may be called multiple times, possibly concurrently, and must handle that. Accessed 20 October 2026.
- Stripe API Reference: Idempotent requests — Idempotency keys make retries safe; V4 UUIDs suggested; avoid personal data as keys; keys may be pruned after 24 hours. Accessed 20 October 2026.
- Stripe Docs: Test clocks (billing simulations) — Simulations advance time to test subscription changes, renewal failures, trials and billing lifecycle webhook handling. Accessed 20 October 2026.
- Stripe Docs: Integration security guide — Handling card data directly might require more than 300 PCI DSS controls; low-risk integrations send payment details directly to Stripe. Accessed 20 October 2026.
- Stripe Docs: Provide a customer portal to your customers — Hosted portal for updating payment methods, changing or cancelling subscriptions and viewing invoices. Accessed 20 October 2026.
- OWASP Top 10:2021, A01 Broken Access Control — Broken access control ranks first; examples include changing an identifier to view another account and missing access control for POST, PUT and DELETE. Accessed 20 October 2026.
- OWASP Cheat Sheet Series: Session Management Cheat Sheet — Renew session IDs after privilege changes; Secure, HttpOnly and SameSite cookie attributes; server-side invalidation at logout. Accessed 20 October 2026.
Recommendations without a source are AI Dev Planner’s own engineering opinion. Platform features and prices change; check the linked pages for the current position. Found an error? See our corrections policy.
Related guides
- Pricing, Metrics & Business ModelFailed Payments and Dunning for SaaS: Retries, Emails and Billing Edge CasesRetry schedules, dunning emails, hard declines, webhook handling and what to do when recovery fails.
- AI, Vibe Coding & the Future of SaaSCan AI Build a Production-Ready SaaS, or Just a Prototype?What AI app builders and coding agents can ship, the gap between a demo and a product, and how to choose between a builder, an agency or both.
- Building & Shipping ProductsWhat Not to Build in a SaaS: Build vs Buy for Auth, Email, Analytics and PaymentsA build-or-buy table for auth, email, billing, analytics, search and more, plus the criteria for deciding.
- Tech Stack & ArchitectureMulti-Tenant SaaS Architecture: Databases, Organisations, Permissions and SSOChoosing a tenant isolation model, and designing organisations, roles, single sign-on and audit logs for a B2B SaaS.
Browse all 76 questions in the SaaS & web development guide.
Not sure your billing or login code is safe?
We review payment, webhook and authentication flows in existing apps, including AI-built ones, and fix what we find. Tell us what you are running and we will scope a review.