THE VAULT / RUN IT LIKE A BUSINESS / GUIDE + PROMPTS
Stripe in an Afternoon
Stripe's docs are 200+ pages and you need about 6 of them to take money today. This is the afternoon version: a decision tree that picks your integration surface in 2 minutes, the exact clicks for Payment Links, one prompt that makes your agent build a webhook handler that actually survives production, and the checklist that keeps your first payout from getting frozen. Everything verified against live Stripe docs and pricing as of July 2026.
LAST VERIFIED 2026-07-08
NOTE
Use this page as a prompt reference. Every file below is built to be handed straight to an AI. Download it, drop it into Claude, ChatGPT, Cursor, or Lovable, and say "use this as my playbook." The prompts on this page are copy-paste ready.
Pick your surface in 2 minutes (most people over-build this)
Stripe gives you three ways to take money and the docs treat them as equals. They are not. They are three stages of the same product, and picking the wrong stage costs you either an afternoon you didn't need to spend or a rebuild in month three. Here's the honest version.
- 1
No app yet, or the app doesn't need to know who paid? Payment Link.
Selling a template, an ebook, a preorder, a "founding member" deal from a landing page or a social bio: Payment Link, zero code, live in 10 minutes. Stripe hosts the page, handles cards and wallets, emails receipts. You can duct-tape access with the post-payment redirect or a manual email. This carries further than you think.
- 2
App needs to unlock features per user? Checkout + webhooks.
The moment your software has to KNOW someone paid (gated features, subscriptions, seats), you need two endpoints: one that creates a Checkout Session, one that receives webhooks. Stripe still hosts the payment page, so you write roughly 150 lines total and never touch a card number. This is the right answer for 90% of SaaS, and it's the path the prompt below builds.
- 3
Checkout page IS the product? Elements. Later.
Custom pricing sliders, payment inside your app's own UI, extra fields mid-checkout: that's the Payment Element, embeddable components where you own the page. Full control, and full ownership of edge cases Stripe's hosted page handles for free (localization, wallet buttons, 3DS flows across 100+ payment methods). If you're reading a beginner guide, you are not at this stage. Note for when you get there: build on the Payment Element, not the legacy Card Element, and use it with the Checkout Sessions API rather than raw Payment Intents; less glue code for the same flows.
| Surface | Code you write | Time to first dollar | Ceiling |
|---|---|---|---|
| Payment Link | None | 10 minutes | No per-user app logic; access is manual or redirect-based |
| Checkout | ~150 lines (2 endpoints + migration) | An afternoon | Covers subscriptions, trials, plan switches, portals; page layout is Stripe's |
| Elements | The whole page + all the edge cases | Days | None, and that's the problem: you own everything |
NOTE
The stage-of-product rule: validating demand = Payment Link. Real users who log in = Checkout. Conversion-optimizing an established funnel = maybe Elements. Moving up a stage later is easy (Checkout reuses your products and prices); starting a stage too high just burns the afternoon.
WATCH OUT
Whatever surface you pick: with hosted Payment Links and hosted Checkout, card data never touches your servers, which keeps your PCI compliance at the self-assessment-questionnaire level. Building custom card forms outside Elements is how you end up owning problems no solo builder should own. Don't.
Payment Links: live in 10 minutes, and usually enough at first
I've watched people spend a weekend wiring webhooks to sell a $29 digital product to an audience of zero. Wrong order. Sell first with a link, integrate when the sales justify it. Here are the exact clicks.
- 1
Create the product
Dashboard, then Product catalog, then + Add product. Name it what the customer should see, set the price. One-time or recurring both work; Payment Links handle subscriptions fine, including trials and promo codes.
- 2
Create the link
Payment Links (left nav), then + New. Pick the product. Options worth touching: allow promotion codes (on), collect customers' addresses (only if you need them for tax), quantity adjustable (for seats or multi-packs). Since the Basil-era API updates you can even let customers set their own amount (ad hoc pricing) for tip-jar or pay-what-you-want setups.
- 3
Set the after-payment behavior
Under confirmation: either Stripe's confirmation page or redirect to your URL. The redirect is your poor man's fulfillment: send buyers to a page with the download link or the Discord invite. Not bulletproof (the URL is shareable), and at $500/mo that is a fine problem to have.
- 4
Test it in test mode
Toggle test mode (top right), open the link, pay with card
4242 4242 4242 4242, any future expiry, any CVC. Confirm the receipt email and the redirect. - 5
Flip to live and ship it
Recreate or copy the product and link in live mode (test-mode objects don't auto-carry). Put the link in the bio, the landing page button, the email. There's a QR code on the link's detail page for anything printed or IRL.
PAYOFF
When a Payment Link is enough: digital products, presales, consulting deposits, community memberships where you grant access by hand, and any launch where you expect fewer than ~10 sales a day. Manual fulfillment at that volume is minutes per day. The upgrade trigger is specific: the day access needs to be granted, revoked, or renewed automatically per user account, move to Checkout.
The math while you're deciding: Stripe takes 2.9% + $0.30 per successful US card charge, plus 1.5% if the card is foreign-issued, plus 1% if currency conversion happens (as of July 2026). On a $29 product you net about $27.86 domestic. No monthly fee at this level, so a dead product costs you $0.
Checkout: one endpoint to charge, one webhook to not get robbed by your own bugs
The Checkout integration is two endpoints. The first one is easy and every tutorial covers it: POST /api/checkout creates a Checkout Session and redirects the user to Stripe's hosted page. The second one, the webhook handler, is where the real state of your business lives, and it's the part agents reliably get subtly wrong. Your app does not know someone paid, renewed, failed a renewal, or canceled unless the webhook tells it.
Why you can't skip webhooks: the success redirect is not proof of payment (users close tabs, networks drop, and nothing redirects on a renewal 30 days later). Renewals, failed cards, and cancellations all happen while nobody is on your site. Webhooks are the only channel for all of it.
The five events that matter (ignore the other 250+)
| Event | When it fires | What your handler does |
|---|---|---|
checkout.session.completed | Purchase finishes | Link the Stripe customer + subscription to your user row. The handshake, not the source of truth. |
customer.subscription.updated | Plan switch, cancel scheduled, trial converts, goes past_due | Sync status, price, period end, cancel_at_period_end to your DB. |
customer.subscription.deleted | Subscription actually ends | Revoke access, mark canceled, keep the history. |
invoice.paid | Every successful charge, including renewals | Extend current_period_end. Provision here too, or month 2 silently breaks. |
invoice.payment_failed | Renewal card declines | Mark past_due, show a fix-payment banner. Don't insta-revoke; Stripe retries per your dunning settings. |
WATCH OUT
The #1 vibe-coded Stripe bug: provisioning only on checkout.session.completed. Everything works in demos, then renewals come and your app never hears about them. Users whose cards fail keep access forever; users who pay on time look expired if you gate on a stored period end. Handle invoice.paid from day one.
The three things your handler must do before any business logic
- 1
Verify the signature against the RAW body
stripe.webhooks.constructEvent(rawBody, signatureHeader, webhookSecret). The SDK does constant-time comparison and enforces a 5-minute timestamp tolerance against replays. The classic failure: your framework JSON-parses the body before your handler sees it (Expressexpress.json(), some Next.js defaults), verification fails on every event, and the agent "fixes" it by skipping verification. Mount raw-body parsing on the webhook route only. - 2
Dedupe by event id
Stripe retries deliveries until it gets a 2xx, so the same event WILL arrive twice eventually. Insert
event.idinto aprocessed_stripe_eventstable with a unique constraint; on conflict, return 200 and do nothing. Without this you get double provisioning, double emails, and eventually a double refund. - 3
Return 200 fast, work async
Slow handlers time out, Stripe retries, and now you're processing duplicates under load. Verify, dedupe, update the DB row, return. Emails and analytics go in a queue or background job.
THE WEBHOOK PROMPT
This is the whole section as one paste. Fill the [SLOTS], give it to your agent, and it builds the checkout endpoint, the webhook handler with everything above baked in, the DB migration, and a Customer Portal endpoint. The review checklist at the end is for YOU, because the agent will claim it did all of this whether it did or not.
The DB fields, and why each one exists
| Field | Why you need it |
|---|---|
stripe_customer_id | The permanent link between your user and Stripe. Create once, reuse forever; two customers for one user is a support nightmare. |
stripe_subscription_id | Lets you look up or modify the sub server-side without searching. |
subscription_status | The single field your app gates on. active and trialing mean access. |
price_id | Which plan they're on. Feature flags per tier read this. |
current_period_end | When access expires if nothing renews. Also what you show on the billing page. |
cancel_at_period_end | They canceled but are paid through the period. Show "ends on [date]" instead of a cancel button, keep access until then. |
NOTE
Test the whole thing locally before deploying: stripe listen --forward-to localhost:3000/api/webhooks/stripe gives you a local webhook secret and streams real events, and stripe trigger invoice.payment_failed fires any event on demand. The full command sequence is in the download.
The go-live test matrix: 60 minutes that prevent your worst launch day
Every failure mode below is cheap to test in the sandbox and expensive to discover from a customer email. Run the whole matrix; it's about an hour. The downloadable checklist has all of it plus the account-prep and week-one items.
- [ ]Happy path: checkout with
4242 4242 4242 4242completes, DB row has customer id + subscription id + status + period end, a gated feature actually unlocks - [ ]Generic decline
4000 0000 0000 0002: error shown, zero DB changes, user stays locked out - [ ]Insufficient funds
4000 0000 0000 9995: same - [ ]3D Secure
4000 0027 6000 3184: the authentication challenge appears and completing it succeeds end to end - [ ]Decline then retry with 4242 creates exactly ONE subscription, not two
- [ ]Cancel mid-period via the Customer Portal:
customer.subscription.updatedarrives with cancel_at_period_end true, access remains until period end, thencustomer.subscription.deletedrevokes it - [ ]Webhook replay: resend a delivered event from Dashboard, Developers, Webhooks; handler returns 200 and does not double-provision
- [ ]Bad signature: a raw curl POST to the endpoint returns 400, not 200 and definitely not a provisioned account
- [ ]Renewal via test clock: advance a test-clock customer past the renewal date;
invoice.paidfires and current_period_end moves forward in your DB - [ ]Failed renewal via test clock: same customer with card
4000 0000 0000 0341attached;invoice.payment_failedfires, status goes past_due, your grace-period rule behaves
Test clocks: simulate a year of billing in 5 minutes
- 1
Create the clock, then the customer
In a sandbox: create a test clock, then create a Customer attached to it (
test_clockparam, or via the Dashboard's clock UI). Order matters; you can't attach a clock to an existing customer. - 2
Subscribe them to your real plan
Use your actual price. Attach the card you want to test: 4242 for clean renewals,
4000 0000 0000 0341(attaches fine, fails when charged) for dunning paths. - 3
Advance time and watch real webhooks fire
"Run simulation" on the subscription page, or
POST /v1/test_helpers/test_clocks/:id/advance. The subscription renews, invoices generate, and real webhook events hit your endpoint exactly as they will in month 2. This is the only way to test an annual renewal without waiting a year, and almost nobody does it.
PAYOFF
The two tests that catch the most real bugs, in my experience watching builders ship Stripe: webhook replay (catches missing idempotency) and failed renewal on a test clock (catches the provision-only-on-checkout bug). If you only have 10 minutes, run those two.
The five mistakes that freeze payouts (all preventable, all common)
First, the baseline so you don't panic at normal behavior: your first payout typically lands 7 to 14 days after your first successful live charge, longer in some industries and countries. That's Stripe's standard new-account schedule, not a freeze. After the first one, US accounts default to 2-day rolling payouts. An actual review or hold is almost always one of these five, and every one is preventable before launch.
WATCH OUT
1. Mismatched business info. Legal name, EIN, website URL, and product description that don't line up (or a half-finished verification you skipped past) are the top trigger for new-account reviews. Fix: complete 100% of live-mode verification BEFORE the first charge, and make the website obviously match the business Stripe thinks you are.
WATCH OUT
2. A statement descriptor nobody recognizes. "JS HOLDINGS LLC" on a card statement for a purchase from acmeapp.com gets disputed by your own happy customers. Disputes cost $15 each and a rising dispute rate is the fastest route to a review and a reserve. Fix: Settings, Business, Public details; make the descriptor your product's name or domain.
WATCH OUT
3. Selling things you can't show you'll deliver. Preorders, long shipping windows, and physical goods with no stated fulfillment timeline read as non-delivery risk, and Stripe holds money against it. Fix: publish shipping/delivery timelines on the site, and if you're preselling, say so explicitly on the checkout page and in your Stripe business description.
WATCH OUT
4. Refund and dispute rate creeping up. One refund is nothing. A refund RATE above a few percent, or disputes above roughly 0.75% of charges, flags the account. Reviews can bring a rolling reserve, commonly reported around 10-25% of volume held on a ~90-day rolling basis (per-account, not officially published). Fix: refund fast and generously when asked (a refund is 100x cheaper than a dispute), make support easy to reach, and don't oversell what the product does.
WATCH OUT
5. Going live before the account is actually ready. No ToS, no refund policy, no contact page, plus a volume spike a new account "shouldn't" have (your launch going well!) is the classic combo that triggers a manual review mid-launch. Fix: publish ToS, privacy, refund policy, and a real contact method before the first live charge, ramp volume roughly in line with what you told Stripe at signup, and if a big launch is coming, tell Stripe support in advance. Also check stripe.com/legal/restricted-businesses before you build; some categories are simply not allowed and no checklist fixes that.
NOTE
If a review does happen: respond to Stripe's information requests within 48 hours with exactly what they asked for. Slow, partial, or argumentative responses are how a 3-day review becomes a 3-week one. Money under review is delayed far more often than it is lost, but only if you engage.
After launch: the three upgrades, each under 30 minutes
Customer Portal in 5 minutes (stop hand-building billing pages)
The Customer Portal is Stripe's hosted self-serve billing page: customers update cards, switch plans, cancel, and download invoices without emailing you. Every hour spent building a custom cancel flow is an hour wasted; the portal does it better and syncs back through the webhooks you already handle.
- 1
Activate and configure
Dashboard, Settings, Billing, Customer portal, Activate. Configure: cancellation behavior (immediate vs end of period; pick end of period), plan switching with proration on/off, which products/prices are switchable, invoice history on, plus your logo and colors.
- 2
No-code option
The portal has a permanent login link (customers authenticate via email code). Put it behind a "Manage billing" link on your site or in receipt emails. Zero code, works today.
- 3
API option (already built if you used the prompt)
POST /v1/billing_portal/sessionswithcustomerandreturn_url, redirect the user to the short-livedurlit returns. The webhook prompt above builds this endpoint as Part 4. - 4
Confirm the loop closes
Cancel a test subscription through the portal and watch
customer.subscription.updatedupdate your DB. If that works, billing support tickets mostly stop existing.
Stripe Tax: when revenue crosses thresholds (not before)
Sales tax obligation (nexus) kicks in per US state and per country as your revenue there crosses thresholds; many US states use $100K/year or 200 transactions as the trigger. Day-one revenue almost never crosses anything, so day one is too early to pay for tax automation. Day 500 might not be.
The pricing that matters (US, as of July 2026): Tax Basic is pay-as-you-go with no monthly fee. On no-code surfaces (Checkout, Payment Links, Billing, Invoicing) it costs 0.5% of transaction volume in places you're registered to collect. Via the API it's $0.50 per transaction (includes 10 calculation calls, $0.05 per extra call). Fees apply where calculation runs, including abandoned checkouts. Tax Complete is a monthly subscription on a 1-year contract that adds registrations and automated filings in 90+ countries.
- 1
Turn on free monitoring now
Stripe Tax's obligation monitoring watches your sales against every jurisdiction's thresholds and alerts you when you're approaching one. This costs nothing and answers the only question that matters early: "do I have a problem yet?"
- 2
Register when it alerts
Register in that jurisdiction yourself (most US states are a self-serve web form), or pay Stripe/a service to do it.
- 3
Then enable calculation
Toggle automatic tax on your Checkout Sessions or Payment Links. Because you built on hosted surfaces, this is one setting, not an integration. Calculation and collection are automatic in 100+ countries; filing the returns is separate (Tax Complete or a partner).
NOTE
Selling to the EU or UK changes the math: there is effectively no minimum threshold for VAT on digital services sold cross-border to consumers there. If a meaningful slice of your customers is European, look at this in month one, not month twelve.
Saving cards for later (one-click upsells, seat expansion)
For subscriptions, Stripe already saves and reuses the payment method; renewals need nothing from you. "Saving cards" as a feature is for the next sale: one-click upsells, buying credits, adding seats without re-entering a card.
- 1
Save during checkout
On a payment-mode Checkout Session, set
payment_intent_data.setup_future_usage: "off_session"and make sure the session is attached to a Customer. The card lands on the customer as a saved payment method, with the customer's consent captured by the hosted page. - 2
Save without a purchase
Use a Checkout Session with
mode: "setup": same hosted page, collects and verifies a card, charges nothing. This is the "add a payment method" button. - 3
Charge it later
Create a PaymentIntent with
customer, the savedpayment_method, andoff_session: true, then confirm. Handle the decline path: off-session charges can fail or require authentication, so listen forpayment_intent.payment_failedand fall back to emailing the customer a link to pay on-session.
WATCH OUT
Never store card numbers yourself, even "encrypted, just temporarily." Stripe stores the card; you store stripe_customer_id and a payment method id. That single design decision is most of your PCI story.
The files
Both downloads are the operational core of this page: the prompt (with the local test loop and the diff-review checklist) and the go-live checklist (test matrix plus account prep plus week one). Fill the slots, run the matrix, take money.
Get the next drop
Every new Vault system ships to the list first. Twice a week, free.