Stripe

How to Add Stripe Payments to Your Vibe-Coded App in 30 Minutes

Add Stripe payments to a vibe-coded app with checkout, webhooks, environment variables, test cards, and launch checks.

Published 2026-06-18.

Run a launch checkRead the blog

Your app works. Users are asking how to pay. You've been dreading this part - payment integration has a reputation for being complex, error-prone, and full of gotchas that only show up in production.

It doesn't have to be. With Stripe's CLI-first tooling and a little help from your AI agent, you can go from free to paid in under 30 minutes. Here's the exact process I use, including commands, code, and the issues you'll hit (and how to fix them).

Why Stripe - And Why Now

Stripe is the default for indie hackers for a reason: it's the only major payment processor built with developers first. Stripe CLI1, Stripe Projects2, and Stripe MCP3 integration mean your AI agent can provision services, manage billing, and handle payments without you writing boilerplate integration code.

Alternatives exist - Paddle4 and Lemon Squeezy5 handle tax and act as "merchant of record" (they're legally the seller, not you). That's useful if you have global users and don't want to deal with VAT. But for your first payment integration, Stripe is simpler and cheaper.

The 30-minute timeline assumes: you have a deployed app (Next.js recommended), a Stripe account, and either Claude Code or Codex running. If you're missing any of these, add setup time accordingly.

Prerequisites (Do These First)

  • Stripe account: Sign up at stripe.com6 - free, no approval needed for test mode
  • Stripe CLI installed:
# macOS
brew install stripe/stripe-cli/stripe

# Windows (PowerShell)
scoop install stripe

# Linux
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg
  • Logged in to Stripe via CLI:
stripe login
# This opens a browser tab - approve the connection
  • Your app deployed somewhere (Vercel, Render, etc.) - you need a public URL for webhook handling

Step 1: Initialize Stripe in Your Project (5 minutes)

The fastest way to get started is Stripe Projects, which auto-generates agent skills and provisioning configs:

# In your project root
stripe projects init

This creates a .stripe/ directory with agent skills and a project config file. Then provision the services you need:

# Add a database (optional - only if you need to store subscription state)
stripe projects add supabase/database

# Add analytics (optional)
stripe projects add posthog/analytics

# Check what got provisioned
stripe projects status

The agent skills written to your project directory let Claude Code or Codex understand your Stripe setup and make changes safely.

Step 2: Tell Your Agent to Integrate Checkout (10 minutes)

Here's the prompt I use with Claude Code:

"Add Stripe Checkout to this Next.js app. I need: 1. A checkout button on the pricing page 2. A server action that creates a Checkout Session 3. A success page that confirms payment 4. A webhook handler for the checkout.session.completed event 5. All using environment variables for the Stripe keys
Use the Stripe agent skills in the .stripe directory for context."

The agent will generate code roughly like this:

app/api/stripe/checkout/route.ts:

import Stripe from 'stripe';
import { NextResponse } from 'next/server';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
});

export async function POST(request: Request) {
const body = await request.json();
const { priceId } = body;

try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1,
},
],
mode: 'subscription', // or 'payment' for one-time
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
});

return NextResponse.json({ sessionId: session.id });
} catch (error) {
console.error('Stripe error:', error);
return NextResponse.json(
{ error: 'Failed to create checkout session' },
{ status: 500 }
);
}
}

app/api/stripe/webhook/route.ts:

import Stripe from 'stripe';
import { NextResponse } from 'next/server';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
});

const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get('stripe-signature')!;

let event;
try {
event = stripe.webhooks.constructEvent(payload, signature, webhookSecret);
} catch (error) {
console.error('Webhook verification failed:', error);
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}

if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Grant access, update database, send welcome email
console.log('Payment successful:', session.customer_email);
}

return NextResponse.json({ received: true });
}

Frontend checkout button:

const handleCheckout = async (priceId: string) => {
const response = await fetch('/api/stripe/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});

const { sessionId } = await response.json();
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
await stripe?.redirectToCheckout({ sessionId });
};

Step 3: Create Products and Prices in Stripe Dashboard (5 minutes)

  • Go to dashboard.stripe.com/products7
  • Click "Add product"
  • Name it (e.g., "Pro Plan")
  • Set pricing - monthly or one-time
  • Copy the Price ID (starts with price_) - you'll use this in your frontend

For a typical SaaS, create two products: a monthly plan and an annual plan with a discount. Stripe handles trial periods, prorations, and cancellations automatically.

Step 4: Set Up Environment Variables (3 minutes)

Add these to your.env.local:

# Stripe keys (get from dashboard.stripe.com/apikeys)
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Your app's public URL
NEXT_PUBLIC_URL=http://localhost:3000

For production, set these in your hosting platform (Vercel: vercel env add).

Step 5: Test Webhooks Locally (5 minutes)

Before deploying, you need to test webhooks locally. Stripe CLI makes this trivial:

# Forward webhooks to your local dev server
stripe listen --forward-to localhost:3000/api/stripe/webhook

This command: 1. Creates a temporary webhook endpoint on Stripe's servers 2. Forwards all events to your local /api/stripe/webhook route 3. Prints the webhook signing secret - copy it to your.env.local as STRIPE_WEBHOOK_SECRET

Now go through a test checkout:

# Trigger a test checkout.session.completed event
stripe trigger checkout.session.completed

Watch your terminal - you should see the event payload logged. If your database update or email sending fires, it's working.

For manual testing: use Stripe's test card numbers: - 4242 4242 4242 4242 - succeeds - 4000 0000 0000 0002 - declines - Any future expiry date, any 3-digit CVC, any ZIP

Step 6: Deploy and Go Live (2 minutes)

  • Commit and push your code
  • Add production environment variables to your hosting platform
  • In the Stripe Dashboard, create a webhook endpoint pointing to https://yourdomain.com/api/stripe/webhook
  • Copy the production webhook secret and update your env vars
  • Toggle your Stripe keys from test to live mode (replace sk_test_ with sk_live_)

Before accepting real money, verify: - [ ] Test checkout works end-to-end in live mode (use a real card, refund immediately) - [ ] Webhook endpoint returns 200 in the Stripe Dashboard - [ ] Success page loads and shows confirmation - [ ] Customer appears in Stripe Dashboard after payment

Common Issues and How to Fix Them

"No signatures found matching the expected signature for payload" You forgot to use request.text() instead of request.json() in the webhook handler. Stripe webhooks need the raw body. The constructEvent function does its own JSON parsing.

"Checkout session ID not found" The Price ID you're sending from the frontend doesn't exist in your Stripe account, or you're using a test Price ID with a live secret key (or vice versa). Test and live are completely separate environments.

"Webhook endpoint returned 400" Check that you're passing the raw request body - not parsed JSON - to constructEvent. If you're using a middleware that parses JSON globally, exclude the webhook route from it.

"CORS error on checkout API call" Your API route needs to handle OPTIONS requests, or the fetch call needs credentials: 'include' if you're using cookies. Most commonly: you're calling http://localhost:3000 from http://127.0.0.1:3000 - they count as different origins.

"Payments work in test but not live" Classic. You hardcoded sk_test_ somewhere, or your environment variables aren't set in production. Check vercel env ls or your platform's env var dashboard. Also: did you create separate webhook endpoints for test and live?

Adding Subscriptions vs One-Time Payments

The code above uses mode: 'subscription' which creates recurring charges. For one-time purchases, change to mode: 'payment' and Stripe handles the rest - no subscription management needed.

For subscriptions, you'll want to handle additional webhook events: - invoice.paid - monthly renewal succeeded, extend access - invoice.payment_failed - card declined, show a "update payment" banner - customer.subscription.deleted - cancelled, revoke access

Next Steps After Your First Payment

Once payments are working, consider these in order of priority:

  • Stripe Tax - auto-calculates and remits sales tax. Enable at dashboard.stripe.com/tax8. Costs 0.5% of transaction volume. Worth it once you hit $5K MRR.
  • Customer portal - Stripe provides a pre-built portal for customers to manage their subscription, update cards, and view invoices. Enable it in the Dashboard, then redirect users to it.
  • Usage-based billing - if your product charges per API call, per seat, or per widget, look into Stripe Metered Billing. More complex but powerful.
  • Multiple payment methods - Stripe supports ACH bank transfers, Apple Pay, Google Pay, and buy-now-pay-later (Klarna, Affirm). Enable the ones your customers ask for.

Ready to start collecting revenue? launchbuddy.com helps you validate your monetization strategy before you write a single line of payment code.

Sources and notes

  1. Stripe CLI: https://docs.stripe.com/stripe-cli
  2. Stripe Projects: https://docs.stripe.com/projects
  3. Stripe MCP: https://www.builder.io/blog/best-mcp-servers-2026
  4. Paddle: https://paddle.com/
  5. Lemon Squeezy: https://lemonsqueezy.com/
  6. stripe.com: https://stripe.com
  7. dashboard.stripe.com/products: https://dashboard.stripe.com/products
  8. dashboard.stripe.com/tax: https://dashboard.stripe.com/tax

Related LaunchBuddy resources

The Solo Founder's Legal and Financial Setup GuideHow to Set Up Payments via Your Agent in CodexHow I Vibe-Coded a Simple SEO Tool to 40K Monthly VisitorsHow to Build Faster with Codex and Claude Code