Guides / Prompting and shipping
How to Add Payments to Your App: Checkout, Subscriptions and Webhooks
How to take payments in an app you built: Stripe Payment Links vs Checkout vs subscriptions, webhooks, test mode, going live, and what to know about tax.
Mythex Team · · 8 min read
The simplest way to add payments to your app is Stripe: use a Payment Link if you just need a "Buy" button and can handle orders by hand, Stripe Checkout if your app needs to know who paid and react automatically, and Checkout in subscription mode plus Stripe's customer portal for recurring billing. In every case except the simplest, you also need a webhook — a server endpoint Stripe calls when a payment succeeds — and you should test everything with Stripe's test keys and test cards before switching to live keys.
This guide explains each option, the webhook part most tutorials skip over, and what to check before taking real money. Stripe details are from Stripe's own documentation as of September 2026.
Why use a payment provider at all
Never collect card numbers in your own forms. Handling raw card data brings you into scope for PCI DSS (the card industry's security standard) and is a serious liability. A payment provider like Stripe hosts the payment form, stores the card, deals with bank authentication, and tells your app the result. Your app only ever sees "customer X paid for Y".
This guide uses Stripe because it's widely used, well documented and supported by most AI app builders. Other providers exist, and some act as a "merchant of record" that takes on sales-tax handling for you; if tax compliance worries you, that model is worth comparing.
The three ways to take payments with Stripe
| Option | Code needed | Your app knows who paid? | Best for |
|---|---|---|---|
| Payment Links | None | Only if you add a webhook | Selling a few fixed products, donations, testing demand |
| Checkout (one-time) | Server code to create a session | Yes, via webhook | Stores, paid downloads, one-off purchases |
| Checkout (subscription) | Server code plus webhook handling | Yes, via webhook | SaaS, memberships, anything recurring |
Payment Links
A Payment Link is a Stripe-hosted payment page you create in the Stripe Dashboard without code. You get a URL you can put behind a button in your app, share in an email, or show as a QR code. Stripe's docs say Payment Links can sell a product, a subscription, or accept a donation, and you can let customers choose what to pay or limit the number of purchases.
The catch: your app doesn't automatically know that a particular logged-in user paid. For a donation page or a small shop where you fulfil orders by hand from Stripe's email notifications, that's fine. For "pay to unlock the Pro features", it isn't.
Stripe Checkout
Checkout is a payment page your server creates on demand for a specific purchase. Your server tells Stripe what's being bought (and optionally which customer is buying), Stripe returns a URL, and you send the customer there. Stripe's docs describe it as supporting both one-time and subscription payments, as a full Stripe-hosted page, a page embedded in your site, or an embedded form.
The flow:
- The customer clicks Buy in your app.
- Your server creates a Checkout Session using your secret key, including an ID that links it to your user or order.
- The customer is redirected to Stripe and pays.
- Stripe redirects them back to your success page.
- Stripe sends a
checkout.session.completedevent to your webhook, and your server marks the order paid or unlocks access.
Step 2 must happen on the server, because it uses your secret key. That's the main reason Checkout needs a backend.
Subscriptions
Subscriptions use Checkout in subscription mode with a recurring price you set up in Stripe. The first payment works like the flow above. After that, renewals, failed cards, upgrades and cancellations happen over time — often when the customer isn't in your app at all — so your app learns about them only through webhooks.
For letting customers manage their own subscription, Stripe offers a hosted customer portal. According to Stripe's docs, customers can update payment methods and billing details, change or cancel subscriptions, and view and download invoices there. You configure it in the Stripe Dashboard and add a "Manage billing" button that sends the customer to it. This saves you building those screens yourself.
Webhooks: the part you can't skip
A webhook is an HTTP request Stripe sends to your server when something happens. (See what a webhook is for the general idea.) Stripe's fulfilment guide is explicit: you can't rely on the success page alone to fulfil orders, because a customer can pay and then lose their connection before that page loads. For subscriptions and delayed payment methods, webhooks are required.
Events worth handling
| Event | What it means | What your app does |
|---|---|---|
checkout.session.completed | Checkout finished | Mark the order paid or start access |
checkout.session.async_payment_succeeded | A delayed method (such as a bank debit) cleared later | Fulfil now |
checkout.session.async_payment_failed | That delayed payment failed | Tell the customer; don't fulfil |
invoice.paid | A subscription payment succeeded, including renewals | Extend access |
invoice.payment_failed | A renewal failed | Warn the user; Stripe may retry |
customer.subscription.updated | Plan changed, or cancellation scheduled | Update the user's plan |
customer.subscription.deleted | Subscription ended | Remove paid access |
Rules for a correct webhook handler
These come straight from Stripe's webhook documentation, and AI-generated handlers sometimes miss one:
- Verify the signature. Each event includes a
Stripe-Signatureheader. Your handler checks it using the endpoint's signing secret (it starts withwhsec_). Without this, anyone could send a fake "payment succeeded" to your server. - Use the raw request body. Signature checks fail if your framework parses or modifies the body first.
- Return a 2xx response quickly, then do slower work. Timeouts count as failures.
- Handle duplicates. Stripe may deliver the same event more than once. Record processed event IDs, or make fulfilment safe to run twice.
- Don't assume order. Stripe doesn't guarantee events arrive in the order they happened.
- Expect retries. In live mode Stripe retries failed deliveries for up to three days, so a short outage doesn't lose payments — but your handler must cope with late events.
Webhooks need a public HTTPS address, so they usually only work once your app is published (Stripe's CLI can forward events to a local machine for developers).
A prompt that covers the whole flow
Add a one-time purchase with Stripe Checkout for the "Pro pack" (price ID in STRIPE_PRICE_ID). Create the Checkout Session on the server using STRIPE_SECRET_KEY from secrets, and include the logged-in user's ID in the session. Add a POST /api/stripe/webhook endpoint that verifies the Stripe signature with STRIPE_WEBHOOK_SECRET using the raw request body, handles checkout.session.completed, and marks that user as having the Pro pack. Make it safe if the same event arrives twice. Success and cancel pages. Never expose the secret key to the browser.
For subscriptions, add: "Use subscription mode with STRIPE_PRICE_ID. Handle invoice.paid, invoice.payment_failed, customer.subscription.updated and customer.subscription.deleted. Add a Manage billing button that opens the Stripe customer portal."
Test mode
Stripe has separate sandbox (test) and live environments with separate keys. Sandbox keys start with pk_test_, sk_test_ or rk_test_; live keys start with pk_live_, sk_live_ or rk_live_. Test transactions don't move money.
Useful test cards from Stripe's testing docs (any future expiry date, any three-digit CVC):
| Card number | Result |
|---|---|
4242 4242 4242 4242 | Payment succeeds |
4000 0000 0000 0002 | Card is declined |
4000 0000 0000 3220 | Requires 3D Secure authentication, then succeeds |
Test at least:
- A successful payment unlocks the right thing for the right user
- A declined card shows a clear message and unlocks nothing
- Closing the browser tab straight after paying still results in access (this proves the webhook works, not just the success page)
- Cancelling on the Stripe page returns the user to your app with nothing charged
- For subscriptions: cancel in the customer portal and confirm access ends when it should
Going live
- Activate your Stripe account with your business and bank details.
- Swap in live keys in your app's secrets. Stripe recommends restricted keys with only the permissions your integration needs, rather than an unrestricted secret key.
- Create a live webhook endpoint pointing at your published app. It has its own signing secret, different from the test one — update it.
- Recreate products and prices in live mode; test-mode IDs don't exist there.
- Make one real small purchase and refund it.
Keep every key in your project's secrets, never in code or chat. Our security checklist covers keys and webhook verification in more detail.
Taxes, refunds and the paperwork
Payments bring obligations that code doesn't solve. This isn't tax advice; check with an accountant.
- Sales tax, VAT and GST. Depending on where you and your customers are, you may need to register, charge and file tax. Stripe Tax can calculate and collect tax at checkout, monitor where your sales may create an obligation, and help with registrations and filing — but responsibility stays with you. Merchant-of-record providers are an alternative that takes on more of this.
- Refunds and disputes. Decide your refund policy before launch and put it on your site. Disputes (chargebacks) happen; keep records of what was bought and delivered.
- Receipts and invoices. Stripe can email receipts; businesses may need proper invoices.
- Fees. Every provider charges per transaction. Check the provider's current pricing page rather than relying on figures in articles.
Which setup fits your app
- Donations or a handful of products: Payment Links. See how to build a nonprofit donation website.
- A store with a cart: Checkout with line items and a webhook. See how to build an online store.
- SaaS or memberships: Checkout in subscription mode, webhooks and the customer portal. See how to build a SaaS MVP and how to price a SaaS product.
- A marketplace paying out to sellers: a different, more regulated setup (Stripe Connect or similar). Plan for it separately.
Adding payments in Mythex
Payments in apps you build on Mythex use your own Stripe account and keys; there's no one-click "Connect Stripe" for your app. The usual flow:
- Get Stripe test keys.
- Add
STRIPE_SECRET_KEY(and the webhook secret later) to project secrets — through/secrets, project settings, or the secrets card the agent shows when it needs a key. - Ask the agent for Checkout using a prompt like the one above.
- Test in Preview with test cards. Publish before registering the webhook, since it needs your public URL.
- After swapping to live keys, publish again so the live app picks them up.
See Accept payments with Stripe and Handle webhooks in your backend in the docs.
Questions
What is the easiest way to accept payments in an app?
A Stripe Payment Link needs no code: you create it in the Stripe Dashboard and link to it from your app. If your app needs to know who paid and unlock something automatically, use Stripe Checkout with a webhook instead.
Do I need a webhook to take payments?
If your app has to react to a payment — unlock an account, mark an order paid, handle a renewal — yes. Stripe's documentation says you can't rely on the success page alone, because a customer may pay and never reach it. Webhooks are also required for subscriptions.
How do I test payments without real money?
Use Stripe's sandbox (test) API keys and its test card numbers, such as 4242 4242 4242 4242 with any future expiry date and any three-digit CVC. Test transactions don't move real money.
Does Stripe handle sales tax and VAT for me?
Stripe Tax can calculate and collect tax at checkout and monitor where you may need to register, but you remain responsible for your tax obligations. Check with an accountant for your situation.