arrow_back Back to Blog
lovable stripe integrationpaymentsproduction

Lovable Stripe Integration: What It Skips

·

The Lovable Stripe integration is genuinely quick. You ask for a pricing page in chat, paste a key into the Connect Stripe form, and ten minutes later a test card (4242 4242 4242 4242, you’ll type it a hundred times) goes through Checkout and your account flips to Pro. I’m not going to pretend that’s bad. For a weekend project it’s exactly right.

The trouble starts when real money and real customers show up, because a few defaults that were fine in the demo are now load-bearing.

First, though, check which of the two payment setups you’re actually on. People mix them up constantly.

Two different things are both called “Stripe in Lovable”

Your own Stripe account. You give Lovable a secret or restricted key, it’s stored as STRIPE_SECRET_KEY in the backend secrets (in Cloud’s Secrets panel, or in your Supabase project’s edge function secrets if you’ve connected your own), and Lovable writes edge functions that call Stripe. You own the account, the customers, the payouts.

Built-in payments. Newer. Lovable creates the payment account for you, either Paddle or Stripe, and you claim it when you go live. Paddle acts as merchant of record at 5% + 50¢ a transaction, which means Paddle deals with sales tax and VAT instead of you. Stripe here runs at normal Stripe pricing. It needs a paid Lovable plan.

Both are legitimate. They fail in different ways.

No webhooks by default

On the own-key setup, the integration doesn’t use webhooks unless you ask for them. The app asks Stripe directly: the user comes back from Checkout, a function looks up their subscription, the UI updates.

That works as long as the user’s browser is the thing driving every change. It falls apart for everything that happens when they aren’t looking. A card fails on renewal in month three. Someone cancels from the Stripe customer portal. A bank disputes a charge. Your database hears about none of it, so anything you do server side on a payment (granting credits, sending the welcome email, provisioning a workspace) only runs if the user happens to land back on the right page with the tab still open.

Add the webhook. Lovable will write the function if you ask, and then you create the event destination in the Stripe dashboard yourself and save the signing secret. The events most apps need are checkout.session.completed, customer.subscription.updated, customer.subscription.deleted and invoice.payment_failed. Not twenty of them. Four.

Three things to check in what gets generated:

  1. It verifies the signature. On Supabase edge functions that means Deno, so it should read the raw body with await req.text() (not req.json(), which breaks the signature) and call stripe.webhooks.constructEventAsync(body, sig, secret, undefined, Stripe.createSubtleCryptoProvider()). The sync constructEvent throws on Deno and somebody “fixes” that by deleting the check. I’ve seen it.
  2. verify_jwt = false is set for the webhook function only, in supabase/config.toml. Stripe doesn’t send your JWT. It’s correct here and wrong almost everywhere else.
  3. It’s idempotent. Stripe retries, sometimes hours later. Store event.id and skip events you’ve already handled, or a retried checkout.session.completed grants the credits twice.

Subscribers are matched by email

This is the one that turns into a support ticket.

Lovable’s docs say it plainly: subscriptions are matched to users by email address. The user signs up as sam@gmail.com, then at Checkout types sam@company.com because that’s where the company card’s receipts should go. Stripe now has a customer with a paid subscription and no user to attach it to. Sam sees the free plan, emails you “I paid and nothing happened”, and you go digging through two dashboards at 11pm.

One app we looked at had a handful of these, every one sorted out by hand in the Stripe dashboard, every one a refund-or-fix conversation. The actual fix took an afternoon. Create the Stripe customer when the user signs up (or on their first checkout), store stripe_customer_id on the profile row, and pass customer (or at least client_reference_id with the user’s ID) when creating the Checkout Session. Then email stops mattering. Users can change their email in your app without losing their plan either, which the email-matching version also gets wrong.

Test mode and the preview

On the own-key setup there’s one key at a time and no separate test environment in the preview. Once you swap sk_test_ for a live key, the preview takes real cards. Teams find this out when someone tests a coupon in the editor and a real charge lands on the founder’s Amex. Keep testing on a remix or a staging copy with test keys, and use a restricted key (rk_live_...) with only the permissions the functions need, rather than the full secret key.

Built-in payments handle this better: separate test and live environments, products synced to live when you publish.

The built-in payments catches

I like Paddle as merchant of record for a solo founder selling software worldwide. Doing EU VAT yourself is miserable and 5% is cheaper than an accountant who knows OSS returns.

But read the fine print before you build on it:

  • On older React + Vite Lovable apps, built-in payments need Lovable Cloud and can’t be used with an external Supabase connection. Lovable’s docs say newer TanStack Start apps (created from May 2026) can use your own Supabase. If you’re planning a move from Lovable Cloud to your own Supabase, work out the payments side first, not after.
  • If you disconnect, that project can’t be paired with the original Paddle or Stripe account again.
  • Projects with payments can’t be remixed.
  • Products are managed through Lovable. Edit a price in the Stripe dashboard directly and the two drift apart.
  • One subscription per user per environment by default. Fine for most SaaS. Not fine if you sell add-ons as separate subscriptions.

None of these are bugs. They’re the price of Lovable doing the setup for you, and for plenty of apps it’s a fair trade.

Which one I’d pick

Selling a digital product globally, no existing Stripe account, staying on Lovable Cloud: built-in payments with Paddle. Already on Stripe, or moving to your own Supabase, or you need anything custom (usage billing, seats, add-ons): your own key, plus webhooks, plus customer IDs instead of emails.

Whichever you pick, the test before launch is the same. Pay with a different email than you signed up with, cancel from the customer portal, and use Stripe’s test card 4000 0000 0000 0341 (attaches fine, declines on charge) to force a failed renewal. If your database is right after all three, you’re fine.

If it isn’t, that’s the kind of production work we do for a fixed price. Book a call and bring your Stripe dashboard.