To use Stripe with V0, generate your checkout UI in V0, then create a Next.js API route at app/api/stripe/route.ts that uses the Stripe Node.js SDK to create checkout sessions. Store your Stripe secret key in Vercel Dashboard → Settings → Environment Variables as STRIPE_SECRET_KEY. For webhooks, use await request.text() to read the raw body before parsing — this is required for Stripe's signature verification.
Accepting Payments in Your V0 App with Stripe
Stripe is the most popular payment processor for apps built with AI tools, and for good reason: its API is well-documented, its test mode lets you simulate every payment scenario without real money, and its Checkout product handles PCI compliance automatically so you do not need to build a secure card form yourself. For V0 users, the integration follows a clear pattern: V0 generates the UI, a Next.js API route talks to Stripe, and Vercel hosts everything.
The most important security rule for any Stripe integration is to keep your secret key out of the browser. V0 generates client-side React components, but Stripe's secret key (which starts with sk_live_ or sk_test_) must only exist in server-side code. In Next.js, this means the API route at app/api/stripe/route.ts — this file runs on Vercel's serverless infrastructure and is never sent to the browser. The secret key lives in Vercel's environment variables, not in your code.
For a complete integration you also need to handle webhooks — events that Stripe sends to your server when things happen asynchronously, like a payment completing or a subscription renewing. The webhook handler has one critical requirement: you must read the raw request body as text before Stripe can verify the event signature. If you use request.json() instead of request.text(), signature verification will fail and you will not be able to confirm payments reliably.
Integration method
V0 generates your React payment UI components while a Next.js API route handles all communication with Stripe's API server-side, keeping your secret key out of the browser. Stripe redirects customers to a Vercel-hosted checkout session URL, then sends webhook events back to your API route to confirm successful payments.
Prerequisites
- A V0 account with a Next.js project generated at v0.dev
- A Stripe account at stripe.com (free to create, test mode available immediately)
- A Vercel account with your V0 project deployed via GitHub
- Your Stripe publishable key (pk_test_...) and secret key (sk_test_...) from Stripe Dashboard → Developers → API keys
- At least one Stripe Price ID from Stripe Dashboard → Products (for subscription billing)
Step-by-step guide
Generate Your Payment UI in V0
Generate Your Payment UI in V0
Start by prompting V0 to generate the front-end interface for your payment flow. You want a React component with a button that will trigger the checkout process. V0 is excellent at generating polished pricing pages, product cards, and checkout buttons with Tailwind CSS styling. The key is to tell V0 the exact API endpoint the button should call so it wires up the fetch request correctly. When you prompt V0, be specific about the UI structure you need. For a simple one-time purchase, a product description page with a single CTA button works well. For subscriptions, a pricing table with multiple tiers is the standard pattern. V0 will generate a React component that makes a POST request to /api/stripe/checkout with a JSON body containing the relevant parameters like priceId or productName. After V0 generates the component, review the fetch call in the generated code. It should look something like: fetch('/api/stripe/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId: 'price_xxx' }) }). The component should then handle the JSON response from the API and redirect the user to the Stripe Checkout URL using window.location.href = data.url. Make sure this redirect logic is present in V0's generated code — if it is missing, ask V0 to add it.
Create a pricing page with two subscription tiers: Pro at $29/month and Business at $99/month. Each tier has a card with the plan name, price, 5 feature bullets with checkmarks, and a Subscribe button. The Subscribe button should POST to /api/stripe/checkout with body { priceId } and redirect to the returned session URL. Show a loading spinner on the button while the request is in progress.
Paste this in V0 chat
Pro tip: Ask V0 to include error handling in the UI — display a friendly message if the checkout API call fails. This prevents users from seeing a blank screen if something goes wrong.
Expected result: V0 generates a polished pricing or product page with a functional button that will call your Stripe API route. The component includes a fetch POST request and redirect logic.
Create the Stripe Checkout API Route
Create the Stripe Checkout API Route
Now create the server-side API route that the V0-generated button will call. In your V0 project's code editor (or after pushing to GitHub and editing locally), create the file app/api/stripe/route.ts. This file runs as a serverless function on Vercel — it has access to your environment variables but its code is never sent to the browser, making it safe to use your Stripe secret key here. The API route does three things: reads the priceId from the request body, creates a Stripe Checkout Session using the Stripe Node SDK, and returns the session URL to the client. You will also need to install the Stripe npm package if it is not already in your project. In your V0 project, you can add it to package.json manually or ask V0 to scaffold the route with the correct import. Important configuration for the Checkout Session: the success_url and cancel_url tell Stripe where to redirect the customer after payment succeeds or if they click Back. These should be full URLs including your domain. For local development, use http://localhost:3000/success and http://localhost:3000. For production on Vercel, use your actual domain. A good pattern is to use the NEXT_PUBLIC_BASE_URL environment variable for the domain so you do not have to hardcode it. The mode parameter determines payment type: 'payment' for one-time charges, 'subscription' for recurring billing. If you are selling subscriptions, the line_items must reference a Price object you created in the Stripe Dashboard, not a raw amount — this is because Stripe's subscription logic ties to recurring Price configurations.
Add a Next.js API route at app/api/stripe/route.ts that creates a Stripe Checkout session. It should accept POST requests with a priceId in the body, use the stripe npm package with STRIPE_SECRET_KEY from environment variables, set success_url to process.env.NEXT_PUBLIC_BASE_URL + '/success', set cancel_url to process.env.NEXT_PUBLIC_BASE_URL, and return the session URL as JSON.
Paste this in V0 chat
1import Stripe from 'stripe';2import { NextRequest, NextResponse } from 'next/server';34const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {5 apiVersion: '2024-12-18.acacia',6});78export async function POST(request: NextRequest) {9 try {10 const { priceId } = await request.json();1112 if (!priceId) {13 return NextResponse.json(14 { error: 'priceId is required' },15 { status: 400 }16 );17 }1819 const session = await stripe.checkout.sessions.create({20 mode: 'subscription',21 line_items: [22 {23 price: priceId,24 quantity: 1,25 },26 ],27 success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,28 cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/pricing`,29 });3031 return NextResponse.json({ url: session.url });32 } catch (error) {33 console.error('Stripe checkout error:', error);34 return NextResponse.json(35 { error: 'Failed to create checkout session' },36 { status: 500 }37 );38 }39}Pro tip: Use the Stripe API version string that matches the Stripe npm package version you installed. Check the Stripe Dashboard → Developers → API versions to see available versions.
Expected result: The API route file exists in your project. When called with a valid priceId, it returns a JSON object with a url property pointing to a Stripe-hosted checkout page.
Add Environment Variables in Vercel
Add Environment Variables in Vercel
Your API route reads STRIPE_SECRET_KEY and NEXT_PUBLIC_BASE_URL from environment variables. You need to add these to Vercel's environment configuration so they are available when your serverless functions run in production. Go to your Vercel Dashboard and open your project. Click the Settings tab at the top, then click Environment Variables in the left sidebar. You will see a form to add key-value pairs. Add the following variables: First, STRIPE_SECRET_KEY with the value of your Stripe secret key from Stripe Dashboard → Developers → API keys. The test key starts with sk_test_ and the live key starts with sk_live_. Use the test key during development. Make sure to set the environment to Production, Preview, and Development so it is available everywhere. NEVER add the NEXT_PUBLIC_ prefix to this key — that would expose it in the browser. Second, NEXT_PUBLIC_BASE_URL set to your Vercel deployment URL, for example https://your-project.vercel.app. This is used in the checkout session's success and cancel URLs. For custom domains, use your actual domain here. The NEXT_PUBLIC_ prefix is correct for this one because it is just your domain name, not a secret. Optionally, add STRIPE_PUBLISHABLE_KEY (without NEXT_PUBLIC_ if you only use it server-side, or with it if your V0 components need it for Stripe.js or Elements). After adding all variables, click Save. You will need to redeploy your Vercel project for the new environment variables to take effect — trigger a new deployment by pushing any commit to your GitHub repository.
Pro tip: For local development, create a .env.local file in your project root with these same variables. Make sure .env.local is in your .gitignore (V0-generated projects include this by default).
Expected result: Vercel Dashboard shows all three environment variables saved. After redeployment, your API route can access process.env.STRIPE_SECRET_KEY without errors.
Create a Webhook Handler for Payment Confirmation
Create a Webhook Handler for Payment Confirmation
Stripe webhooks are how your server learns about events that happen asynchronously — like a payment completing, a subscription renewing, or a card being declined after initial signup. Without webhooks, your app has no reliable way to confirm a payment actually succeeded. The Checkout Session redirect to your success URL is not sufficient for confirmation because users can manually navigate to that URL without paying. Create a separate API route at app/api/stripe/webhook/route.ts to handle these events. There is one critical requirement that trips up many developers: to verify the webhook signature (which proves the event actually came from Stripe and not from a malicious actor), you must read the request body as raw text using await request.text(). If you call request.json() first, the body is consumed and the signature check will always fail. In the Stripe Dashboard → Developers → Webhooks, create a new webhook endpoint pointing to your Vercel deployment URL: https://your-project.vercel.app/api/stripe/webhook. Select the events you want to receive — at minimum, checkout.session.completed for one-time payments, and customer.subscription.updated plus invoice.paid for subscriptions. Stripe will give you a Webhook Signing Secret (whsec_...). Add this to Vercel's environment variables as STRIPE_WEBHOOK_SECRET. The webhook handler should be idempotent — safe to call multiple times for the same event. Stripe may send the same event more than once if your server does not respond with a 200 status quickly. Store the Stripe event ID in your database and check it before processing to avoid duplicate order fulfillment.
Add a webhook handler at app/api/stripe/webhook/route.ts. It should read the raw request body with await request.text(), verify the Stripe webhook signature using STRIPE_WEBHOOK_SECRET from environment variables, handle checkout.session.completed events by logging the session details, and return a 200 response. Use the stripe npm package.
Paste this in V0 chat
1import Stripe from 'stripe';2import { NextRequest, NextResponse } from 'next/server';34const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {5 apiVersion: '2024-12-18.acacia',6});78// CRITICAL: export the config to disable body parsing9// Next.js App Router does not need this, but raw text reading is essential10export async function POST(request: NextRequest) {11 // Read raw body as TEXT — not JSON. Required for signature verification.12 const body = await request.text();13 const signature = request.headers.get('stripe-signature');1415 if (!signature) {16 return NextResponse.json({ error: 'Missing signature' }, { status: 400 });17 }1819 let event: Stripe.Event;2021 try {22 event = stripe.webhooks.constructEvent(23 body,24 signature,25 process.env.STRIPE_WEBHOOK_SECRET!26 );27 } catch (err) {28 console.error('Webhook signature verification failed:', err);29 return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });30 }3132 // Handle specific events33 switch (event.type) {34 case 'checkout.session.completed': {35 const session = event.data.object as Stripe.Checkout.Session;36 console.log('Payment completed:', session.id);37 // TODO: Fulfill the order — grant access, send email, update database38 break;39 }40 case 'customer.subscription.updated': {41 const subscription = event.data.object as Stripe.Subscription;42 console.log('Subscription updated:', subscription.id, subscription.status);43 break;44 }45 case 'invoice.paid': {46 const invoice = event.data.object as Stripe.Invoice;47 console.log('Invoice paid:', invoice.id);48 break;49 }50 default:51 console.log('Unhandled event type:', event.type);52 }5354 return NextResponse.json({ received: true });55}Pro tip: Use the Stripe CLI locally to forward webhook events to your development server: `stripe listen --forward-to localhost:3000/api/stripe/webhook`. This simulates real webhook delivery without deploying.
Expected result: The webhook handler successfully verifies Stripe event signatures and logs checkout completions. Stripe Dashboard shows webhook deliveries with 200 status responses.
Test the Full Payment Flow
Test the Full Payment Flow
Before going live, thoroughly test the complete payment flow using Stripe's test mode. Stripe provides special test card numbers that simulate every payment scenario without charging real money. The most important test card is 4242 4242 4242 4242 with any future expiration date, any 3-digit CVC, and any 5-digit ZIP — this card always succeeds. Stripe also provides cards that simulate declines (4000 0000 0000 0002 for a generic decline), cards that require 3D Secure authentication (4000 0025 0000 3155), and cards that simulate insufficient funds (4000 0000 0000 9995). Test the happy path first: click the checkout button on your V0-generated page, complete the Stripe Checkout form with test card 4242..., verify you land on your success page, and check the Stripe Dashboard → Payments to confirm the test payment appears. Then check your Vercel logs (Vercel Dashboard → your project → Functions → View Logs) to see if the webhook was received and processed. For complex Stripe integrations with subscription management, customer portals, and metered billing, the configuration can get involved. RapidDev's team specializes in helping non-technical founders set up production-ready Stripe integrations with V0 — from webhook handling to subscription lifecycle management. When you are ready to go live, switch your environment variables in Vercel from test keys (sk_test_, pk_test_, whsec_test_) to live keys (sk_live_, pk_live_, whsec_). Update your Stripe webhook endpoint in the Dashboard to use your live Vercel URL if it is different from your preview URL. Run one real transaction with a real card for a small amount to confirm end-to-end before promoting your app widely.
Add a /success page that displays a friendly confirmation message after Stripe checkout. Show the customer a 'Thank you for your purchase!' heading, a brief description that they will receive a confirmation email, and a button to return to the home page.
Paste this in V0 chat
Pro tip: Stripe test mode and live mode are completely separate environments with separate API keys, customers, and payment records. Never use live keys during development.
Expected result: A complete test payment flow works end-to-end: clicking the button opens Stripe Checkout, entering test card details completes the payment, you land on the success page, and the Vercel logs show the webhook was processed.
Common use cases
One-Time Product Purchase
A founder uses V0 to generate a product page with a 'Buy Now' button. Clicking the button calls a Next.js API route that creates a Stripe Checkout session for a one-time payment. Stripe hosts the payment page, handles card collection, and redirects the customer back to the app after payment.
Create a product page for a digital course called 'Build Your First SaaS'. Include a hero section with the course title and description, a list of what's included, and a prominent 'Buy Now' button that sends a POST request to /api/stripe/checkout.
Copy this prompt to try it in V0
SaaS Subscription Pricing Page
A SaaS founder generates a pricing page with three subscription tiers using V0. Each plan's button triggers a Stripe Checkout session with the correct price ID, creating a monthly or annual subscription. Stripe manages billing cycles and the app listens for subscription events via webhooks.
Build a pricing page with three subscription tiers: Free ($0/mo), Pro ($29/mo), and Business ($99/mo). Each tier lists 5 features with checkmarks. The Pro and Business buttons should call /api/stripe/checkout with a priceId parameter.
Copy this prompt to try it in V0
Digital Download with Payment Gate
A creator sells PDF templates or software tools. V0 generates the storefront UI and a thank-you page. After Stripe confirms payment via webhook, the API route grants the customer access to the download link by recording their email in the database.
Create a landing page for a Notion template bundle. Include a preview section showing 3 template thumbnails, pricing ($49 one-time), and a checkout button. Show a confirmation message after successful payment redirect from Stripe.
Copy this prompt to try it in V0
Troubleshooting
Webhook returns 400 'No signatures found matching the expected signature for payload'
Cause: You are calling request.json() before passing the body to stripe.webhooks.constructEvent(). JSON parsing consumes the raw body, and Stripe's signature verification requires the exact original raw bytes.
Solution: Change your webhook handler to use const body = await request.text() and pass body (the raw string) to constructEvent(). Never call request.json() in a webhook handler.
1// WRONG — causes signature verification failure2const body = await request.json();34// CORRECT — preserves raw body for signature verification5const body = await request.text();API route returns 500 with 'No such price: price_xxx' from Stripe
Cause: The price ID in your request does not exist in Stripe, or you are mixing test mode and live mode. A price created in test mode has a different ID than the same price in live mode.
Solution: Go to Stripe Dashboard → Products and copy the correct Price ID for your current mode (test or live). Update the priceId values in your V0 component or database to match. Verify your STRIPE_SECRET_KEY environment variable is for the same mode (test vs live) as the price IDs you are using.
Stripe Checkout redirects to a URL with localhost in production
Cause: Your success_url or cancel_url is hardcoded with localhost:3000 or uses a NEXT_PUBLIC_BASE_URL environment variable that was not set correctly in Vercel.
Solution: In Vercel Dashboard → Settings → Environment Variables, verify NEXT_PUBLIC_BASE_URL is set to your actual production domain (e.g., https://your-project.vercel.app). Redeploy after updating environment variables.
'Module not found: Can't resolve stripe' after pushing to Vercel
Cause: The stripe npm package is not listed in package.json, so Vercel's build process does not install it.
Solution: Add stripe to your package.json dependencies. In your project root, the dependencies section should include '"stripe": "^17.0.0"' (use the latest version). Commit and push package.json to trigger a new Vercel build.
1// package.json2{3 "dependencies": {4 "stripe": "^17.0.0",5 "next": "15.0.0",6 "react": "^19.0.0"7 }8}Best practices
- Always use await request.text() in webhook handlers — never request.json() — to preserve the raw body for Stripe signature verification.
- Store STRIPE_SECRET_KEY without any NEXT_PUBLIC_ prefix so it is never exposed to the browser.
- Use Stripe's idempotency keys for checkout session creation to prevent duplicate charges if a network retry occurs.
- Test every payment scenario with Stripe's test cards before going live, including declines and 3D Secure flows.
- Handle webhook events idempotently — check the event ID in your database before processing to prevent duplicate order fulfillment.
- Use separate Stripe restricted keys for different environments (test for development, live for production) and rotate them if exposed.
- Enable Vercel's built-in Stripe integration from the Vercel Marketplace for simplified environment variable management.
- Never fulfill orders based on the Checkout redirect alone — always wait for the checkout.session.completed webhook event as the authoritative confirmation.
Alternatives
Spocket is an alternative if you need dropshipping product sourcing alongside payments, rather than a pure payment processor.
Teachable is an alternative if you are selling online courses and want built-in payment processing, student management, and course hosting without custom code.
WooCommerce is an alternative if your storefront is WordPress-based and you want integrated e-commerce without a separate payment API.
Frequently asked questions
Can I use Stripe with V0 without writing any API routes?
Stripe Payment Links let you create a payment URL in the Stripe Dashboard without any code at all — you can link directly to a Stripe-hosted page from your V0 UI. However, for dynamic pricing, subscriptions, or post-payment actions like granting access, you will need a Next.js API route. The API route approach gives you full control over the payment flow.
Why does my Stripe webhook keep failing with signature errors?
The most common cause is using request.json() to read the body instead of request.text(). Stripe's signature is computed over the exact raw bytes of the request body, so any transformation (like JSON parsing and re-serializing) changes the bytes and breaks verification. Always read the body with await request.text() in your webhook handler, then pass that raw string to stripe.webhooks.constructEvent().
Does V0 generate Stripe code automatically?
V0 can generate Stripe-ready UI components and even API route boilerplate when you prompt it specifically. Ask V0 to create a checkout button that calls a specific API route, and it will wire up the fetch request. You still need to write the server-side API route logic yourself (or prompt V0 for that too) and set up the environment variables in Vercel.
What is the difference between Stripe test mode and live mode?
Test mode uses sk_test_ keys and allows you to simulate payments without real money using Stripe's test card numbers. Live mode uses sk_live_ keys and processes real charges. The two modes are completely separate — customers, payments, and webhooks in test mode do not appear in live mode. Always develop and test with test mode keys before switching to live.
How do I add Stripe to my Vercel project via the Marketplace?
In your Vercel Dashboard, go to your project and click the Integrations tab, or visit the Vercel Marketplace at vercel.com/marketplace. Search for Stripe and follow the one-click installation. This automatically provisions your Stripe keys as Vercel environment variables, saving you the manual copy-paste step. It works best for new projects.
Can V0 handle recurring subscriptions with Stripe?
Yes, V0 can generate the subscription pricing UI and you can build the backend logic with a Next.js API route. For subscriptions, set mode: 'subscription' in your Checkout Session and reference a recurring Price ID (not a one-time price). You will also want to handle the customer.subscription.updated and invoice.paid webhook events to track subscription status in your database.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation