Guides / Prompting and shipping
How to Add Subscriptions with Stripe: Plans, Trials, Webhooks and Failed Payments
Add recurring billing with Stripe: Checkout in subscription mode, trials, the customer portal, the webhook events to handle, and what to do when a card fails.
Mythex Team · · 6 min read
To add subscriptions with Stripe, create a product with a recurring price in the Stripe Dashboard, send customers to Stripe Checkout in subscription mode from your server, and let a webhook tell your app when each payment succeeds, fails, changes or ends. Store the subscription status against the user and grant access from that, not from the success page. Add a Manage billing button that opens Stripe's hosted customer portal so people can change cards, switch plans and cancel without you building those screens.
This guide builds on how to add payments to your app, which covers one-time Checkout, test mode and going live. Here we focus on what's different about recurring billing. Stripe details are from Stripe's documentation as of September 2026.
Why subscriptions are harder than one-time payments
A one-time payment happens while the customer is in your app. A subscription keeps happening after they leave: renewals every month, cards that expire, upgrades mid-cycle, cancellations that take effect at period end. Your app only hears about these through webhooks. Most subscription bugs come from treating the first payment as the whole story.
Your options
| Approach | What you build | Best for |
|---|---|---|
| Payment Link with a recurring price | Nothing but a link | Testing demand; handling access by hand |
| Checkout (subscription mode) + customer portal | A server route, a webhook, a billing button | Most SaaS apps and memberships |
| Subscriptions API with your own payment form | Much more: payment UI, authentication, retries | Apps with unusual billing flows |
| Merchant-of-record provider | Similar to Checkout | Sellers who want the provider to handle sales tax |
For most apps, Checkout plus the portal is the right trade-off. Compare providers in Stripe vs Paddle vs Lemon Squeezy if tax handling worries you.
Plan your pricing in Stripe first
In Stripe, a product is what you sell ("Pro plan") and a price is how you charge for it ("$20 per month", "$200 per year"). One product can have several prices. Decide:
- Which tiers you offer and what each unlocks in your app
- Monthly, yearly, or both
- Whether you offer a trial, and whether it needs a card upfront
Create the products and prices in test mode, and keep their price IDs; your app refers to them. If you haven't settled pricing, read how to price a SaaS product and freemium vs free trial.
The subscription lifecycle you must handle
Stripe's subscriptions overview lists the statuses a subscription can have. The ones that matter most for access:
| Status | What it means | Give access? |
|---|---|---|
trialing | In a trial period | Yes |
active | In good standing | Yes |
incomplete | First payment not yet completed; Stripe gives the customer 23 hours | No |
incomplete_expired | First payment never succeeded | No |
past_due | Latest payment failed or wasn't attempted; retries may follow | Your choice; many apps allow a short grace period |
unpaid | Retries finished, invoices stay open, no further attempts | No, Stripe's docs say to revoke access |
canceled | Ended; can't be restarted | No |
paused | A trial ended without a payment method and was set to pause | No |
Store the status on your user or account record and base access on it. Update it every time a relevant webhook arrives.
The webhook events to handle
| Event | What your app does |
|---|---|
checkout.session.completed | Link the new Stripe customer and subscription IDs to your user |
invoice.paid | Mark the subscription active and record the new period end |
invoice.payment_failed | Tell the user their card failed and link to the portal |
invoice.payment_action_required | Ask the user to complete card authentication |
customer.subscription.updated | Update plan, status, and any scheduled cancellation |
customer.subscription.deleted | Remove paid access |
customer.subscription.trial_will_end | Remind the user before the trial ends; Stripe sends it three days before |
Handler rules from Stripe's webhook docs still apply: verify the signature using the raw body, return a 2xx quickly, handle duplicate and out-of-order events, and expect retries. The what is a webhook guide explains the idea, and the payments guide lists the rules in detail.
Step by step
- Create products and prices in Stripe test mode.
- Add secrets: the secret key, the price IDs, and later the webhook signing secret. Never put them in frontend code.
- Pricing page: each plan's button calls your server, which creates a Checkout Session in subscription mode for the logged-in user. Pass your user ID so the webhook can match the payment to the account.
- Webhook endpoint: verify signatures and handle the events above, updating one subscription record per user.
- Gate features on the stored status and plan, checked on the server for every protected action, not just hidden buttons.
- Customer portal: configure it in the Stripe Dashboard (which plans people can switch to, whether they can cancel), then add a Manage billing button that creates a portal session on the server and redirects to it.
- Failed payments: in Stripe's billing settings, choose retry behaviour and what happens after the final retry. Show an in-app banner while
past_due. - Test the whole lifecycle, then publish and register the live webhook.
Prompts to give your AI builder
Add subscription billing with Stripe. Plans: Starter (price ID in STRIPE_PRICE_STARTER) and Pro (STRIPE_PRICE_PRO). The pricing page creates a Checkout Session in subscription mode on the server using STRIPE_SECRET_KEY, with the logged-in user's ID attached. Save the Stripe customer ID, subscription ID, plan and status on the user. Never expose the secret key to the browser.
Add POST /api/stripe/webhook. Verify the signature with STRIPE_WEBHOOK_SECRET using the raw request body. Handle checkout.session.completed, invoice.paid, invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted and customer.subscription.trial_will_end. Record processed event IDs so duplicates are ignored. Update the user's status from the subscription object, not from the order events arrive in.
Gate Pro features on the server: allow them only when status is active or trialing and the plan is Pro. Show a "payment failed, update your card" banner when status is past_due. Add a Manage billing button that opens the Stripe customer portal for this user.
Upgrades, downgrades and proration
When a customer switches plans, Stripe's docs say the change often creates a proration: the new price is applied to the rest of the current billing period. You can preview prorations or turn them off. If the plans have the same interval, billing dates stay the same; moving from monthly to yearly moves the billing date to the day of the switch. Letting customers change plans in the portal saves you writing this logic, but decide in the portal settings which switches are allowed.
Testing checklist
Use Stripe test mode and test cards, and walk through every state:
- New subscription grants the right plan to the right user
- Closing the tab straight after paying still grants access (proves the webhook works)
- A declined card on signup grants nothing
- A renewal that fails puts the account in past_due and shows the banner
- Updating the card in the portal restores active status
- Upgrading and downgrading change features correctly
- Cancelling at period end keeps access until the end date, then removes it
- Trial users get access, get the reminder, and convert or lose access correctly
- Sending the same webhook event twice changes nothing
Stripe's test clocks let you simulate time passing, so you can test renewals without waiting a month. Check Stripe's current testing docs for how to use them.
Common mistakes
- Granting access on the success page. Customers may pay and never reach it. Use the webhook.
- Only handling the first payment. Renewals, failures and cancellations arrive later, as events.
- Checking plan status only in the frontend. Anyone can call your API directly.
- One user, many subscriptions by accident. Check for an existing subscription before creating a new Checkout Session, or send existing customers to the portal instead.
- Revoking access at the first failed renewal. Cards fail for boring reasons. A short grace period with a clear banner keeps customers.
- Forgetting the live webhook. Test and live endpoints have different signing secrets.
- Ignoring tax and refunds. Decide your refund policy and check your tax obligations with an accountant before launch.
Subscriptions in Mythex
Apps you build on Mythex use your own Stripe account and keys; there is no one-click Stripe connection for your app. Add your test keys and price IDs as project secrets, use prompts like the ones above, and test with Stripe's test cards in Preview. Webhooks need your app's public URL, so publish before registering the endpoint in Stripe, and publish again after you switch to live keys so the live app picks them up. See Accept payments with Stripe and Handle webhooks in your backend. For the wider product, how to build a SaaS MVP covers what to build around billing.
Questions
What is the simplest way to add subscriptions with Stripe?
Create a product with a recurring price in the Stripe Dashboard, send customers to Stripe Checkout in subscription mode, handle subscription events with a webhook, and link a Manage billing button to Stripe's hosted customer portal. That avoids building card forms and plan-change screens yourself.
Which Stripe webhook events do I need for subscriptions?
At minimum checkout.session.completed to link the new subscription to your user, invoice.paid to confirm each successful payment, invoice.payment_failed to warn about failed renewals, and customer.subscription.updated and customer.subscription.deleted to track plan changes and cancellations.
What happens when a subscription payment fails?
According to Stripe's documentation as of September 2026, the subscription usually becomes past_due and Stripe can retry the payment based on your settings. After the final retry, your Dashboard settings decide whether it is canceled, marked unpaid, or left past_due.
Can I offer a free trial without asking for a card?
Yes. Stripe supports free trials, and its docs describe a paused status for trials that end without a payment method when configured that way. Collecting a card upfront usually means fewer sign-ups but more paying customers; test what suits your product.