How-To Guides6 min read

How to Add Payments to a Lovable or v0 Site

Lovable and v0 give you real React apps, which means you can wire Stripe properly. It also means you now own the secret keys, the webhooks, and every way that goes wrong.

KindLumen TeamApril 2, 2026Updated April 2, 2026

Quick answer

  • Code-first AI builders can integrate Stripe, but you own the backend.
  • Never put a Stripe secret key in client React — it must be server-side.
  • For donations, an embed skips the whole webhook-and-keys problem.

Here’s what makes Lovable, v0, and Bolt.new different from Framer or Webflow: they generate real apps. Actual React and Next.js, with server routes you can run code on. That’s genuinely powerful, and it means yes, you can integrate Stripe and take real payments.

It also means the safety rails are off. The moment you have a backend, you’re responsible for the parts that go wrong quietly and expensively: where the secret key lives, whether the webhook fires, what happens when a card declines. The AI will happily scaffold all of this, including the parts that are wrong.

This walks the real Stripe path on a code-first AI app, the specific ways it bites, and the faster route for when you just need donations rather than a custom checkout.

website planning notes and campaign materials on a desk for How to Add Payments to a Lovable or v0 Site

What changes when your app has a backend

On a design-first builder, there’s no server, so there’s nothing to secure — you embed and you’re done. On Lovable, v0, or Bolt, you have API routes and server functions, which is exactly what Stripe integration needs. The catch is that "can run server code" and "is running it safely" are different things, and the gap is where money gets lost or stolen.

A real Stripe integration has three parts you now own: a client piece that collects the card, a server piece that creates the charge using your secret key, and a webhook that confirms the payment and triggers fulfillment. Skip or misplace any one and you get a flow that looks fine in a demo and fails in production.

The one rule: the secret key never touches the browser

If you remember nothing else, remember this. Stripe gives you two keys: a publishable key (safe in the browser) and a secret key (must stay on the server). The secret key can move money, issue refunds, and read your account. Anyone who gets it can drain you.

AI builders generate client-side React by default, and when you ask one to "add Stripe," it will sometimes drop the secret key right into a component or a public environment variable. In a Next.js app, anything not prefixed correctly as a server-only variable can leak. The rule is blunt:

// CLIENT (browser) — fine to expose
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...

// SERVER ONLY — never NEXT_PUBLIC, never in a component
STRIPE_SECRET_KEY=sk_live_...

Read every line of AI-generated payment code for where the secret key ends up. If it’s importable from anything that ships to the browser, stop and move it to a server route. This is the single most common and most costly mistake on AI-built payment flows.

The real Stripe path, end to end

Here’s what actually has to exist for a working payment on a code-first app. None of it is exotic, but all of it has to be there.

  1. A server route that creates the payment. Using your secret key on the server, you create a PaymentIntent or a Checkout Session and return only the safe client secret or session URL to the browser.
  2. A client form that collects the card with Stripe’s own elements, so raw card numbers never hit your server.
  3. A webhook endpoint that Stripe calls when the payment succeeds. This is where you send the receipt, record the gift, and start any subscription. Without it, the charge happens and nothing downstream does.
  4. Webhook signature verification so someone can’t fake a "payment succeeded" call to your endpoint.
  5. Error handling for declines, retries, and failed subscription renewals.

The part people miss is the webhook. A demo "works" because you watched the charge go through, but in production the receipt and the donor record depend entirely on the webhook firing and being handled. No webhook, no fulfillment. Test it with Stripe’s CLI before you trust it.

Where AI-generated payment code falls down

The AI is great at the happy path and quietly bad at the edges. Watch for these:

  • Secret key in the wrong place. Already covered, but it’s the number one issue, so it’s worth saying twice.
  • No webhook at all. The generated code charges the card and assumes success client-side, so receipts and records silently never happen.
  • Unverified webhooks. An endpoint that trusts any POST is a hole.
  • Subscriptions faked as one-time charges for "monthly" giving, so nothing actually renews.
  • No idempotency, so a retried request can double-charge.

You can fix all of these. Just budget for it being a real task with real testing, not a one-prompt finish. Treat the AI’s first pass as a draft a junior dev wrote on a deadline.

The faster path: embed instead of build

Now the honest question: do you actually need a custom checkout, or do you just need to take donations? If it’s the latter, building all of the above is reinventing a solved problem.

Because Lovable, v0, and Bolt produce real apps, they can also just render an embed — a hosted donation form drops into a component as easily as into a Framer page. The secret key, the webhooks, the receipts, and the donor records all live on the form provider’s backend, not yours. You connect your own Stripe account so payouts still go straight to you, and you skip every gotcha above.

export function DonateSection() {
  return (
    <div data-donation-form="your-org"></div>
    // plus the provider’s script tag in your layout
  )
}

That’s the trade: with the DIY path you get total control and own all the risk; with an embed like KindLumen you give up custom-checkout flexibility and get a working, branded donation flow today with no key to leak. For most "my AI app needs donations" cases, the embed is the right answer.

Build versus embed, decided

Wire Stripe yourself when payments are core to what you’re building — a marketplace, a product with complex checkout, something where the payment flow is the feature and you’ll iterate on it. There, the control is worth owning the backend.

Embed when donations are a feature on an otherwise-finished site, you want it live this week, and you’d rather not be the person responsible for a leaked secret key. The pillar piece adding donations to an AI-generated website covers the same fork across all the AI builders, design-first and code-first. Either way: test with a real card on the live deploy, refund it, and verify the receipt actually arrived before you call it done.

Frequently asked questions

Can Lovable, v0, and Bolt really integrate Stripe?

Yes. They generate real React/Next.js apps with server routes, which is what Stripe needs. The capability is real — the responsibility for secret keys, webhooks, and error handling now sits with you, not the builder.

Why can’t I put the Stripe secret key in my React code?

Because anything in client code ships to the browser, where anyone can read it. The secret key can move money and issue refunds, so a leak can drain your account. It must live in a server-only environment variable and be used only in server routes.

What happens if I skip the webhook?

The card can still be charged, but nothing downstream fires — no receipt, no donor record, no started subscription. A demo will look like it works while production silently drops fulfillment. No webhook, no fulfillment.

Should I build the Stripe flow or embed a form?

Build it if a custom payment flow is core to your product and you’ll iterate on it. Embed a hosted form if you just need donations on a finished site — it moves the secret key, webhooks, and receipts off your plate entirely.

Product links

Use the research, then choose the right donation setup.

Compare your options, then move into a donation setup your team can launch and maintain with confidence.

Related reading