Take payments with Stripe: Checkout, webhooks and subscriptions
Add payments to your website or app with Stripe: products and prices, Checkout, webhooks that confirm payment, subscriptions, testing without real money and going live.
Stripe is the standard way for a small team to take card payments online: one-off purchases, subscriptions, invoices and refunds, with the card details handled entirely by Stripe so you never store them yourself. It's available to Australian businesses, and its test mode lets you build the whole flow without moving real money.
Pick the simplest option that works
- Payment Links: no code at all. Create a link in the dashboard and put it on a button. Great for selling a few fixed products.
- Checkout: a Stripe-hosted payment page your app sends people to. It handles cards, Apple Pay and Google Pay, receipts and security checks. This is the right choice for most apps.
- Elements: payment fields embedded in your own page. More control, more work. Leave it until you have a reason.
Set up your account
Create an account and stay in test mode while you build. To accept real payments you'll add your business details (an ABN if you have one) and a bank account for payouts. Pricing and GST depend on your situation: check how you need to handle GST with your accountant, and look at Stripe Tax if you sell across borders.
Create your products and their prices in the dashboard. Each price has an ID starting with price_. That's what your code uses.
Know your keys
- Publishable key (
pk_test_…/pk_live_…): safe in the browser. - Secret key (
sk_test_…/sk_live_…): server only. It can charge cards and issue refunds. Keep it in environment variables, never in front-end code or Git.
How a Checkout payment flows
- The customer clicks "Buy" on your site.
- Your server creates a Checkout Session with your secret key and gets back a URL.
- You redirect the customer to that Stripe page; they pay.
- Stripe sends them back to your success page.
- Stripe also sends your server a webhook saying the payment completed, and that's when you give them access.
The server step looks like this (Node.js):
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const session = await stripe.checkout.sessions.create({
mode: 'payment', // 'subscription' for recurring prices
line_items: [{ price: 'price_123', quantity: 1 }],
success_url: 'https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/pricing',
});
// send the customer to session.url
Webhooks: the step people skip
Never treat a visit to your success page as proof of payment; anyone can type that address. The source of truth is the webhook Stripe sends to your server. Your webhook endpoint should:
- Verify the signature using your webhook signing secret (
whsec_…) and the raw request body, so you know the message really came from Stripe. - Handle the events you need, typically
checkout.session.completed, and for subscriptionscustomer.subscription.updated,customer.subscription.deletedandinvoice.payment_failed. - Be safe to run twice. Stripe retries webhooks, so check whether you've already processed an event before granting anything again.
- Update your database, for example marking the user as paid in Supabase. Do this with your server-side key, never from the browser.
Subscriptions
For recurring billing, create a recurring price and use mode: 'subscription'. Store the Stripe customer ID and subscription status against the user in your database, keep them in sync from webhooks, and let people manage cards and cancellations through Stripe's hosted customer portal instead of building those screens yourself.
Test without real money
- Use test-mode keys and the test card
4242 4242 4242 4242with any future expiry date and any CVC. Stripe's docs list cards for declines and extra authentication too. - Install the Stripe CLI and run
stripe listen --forward-to localhost:3000/api/webhooks(use your own route) to send test webhooks to your computer. It prints a signing secret to use locally. - Trigger events on demand with
stripe trigger checkout.session.completed.
Let Claude Code wire it up
A good prompt names the flow, the files and the safety rules:
Add Stripe Checkout for the "Pro" plan (price ID in STRIPE_PRICE_PRO).
1. A server route that creates a subscription Checkout Session for
the logged-in user and returns the URL.
2. A webhook route that verifies the Stripe signature with the raw body
and, on checkout.session.completed and subscription updates, sets
profiles.plan and profiles.stripe_customer_id in Supabase using the
server-side key. Make it safe to receive the same event twice.
3. A link to the Stripe customer portal on the account page.
Never expose the secret key to the browser. Show me the plan first.
Going live
- Test and live mode are separate: recreate your products and prices in live mode and update the price IDs.
- Swap in live keys in your host's environment variables.
- Add a live webhook endpoint in the dashboard and use its new signing secret.
- Make one real purchase and one refund before you announce anything.
Next: connect other APIs safely, using the same server-side pattern for everything else.