---
title: "Lovable Stripe integration vs built-in payments | Lovable Field Guide"
description: "Lovable Stripe is set up through prompts, and it's a different thing from Lovable's built-in payments. How to choose, plus the webhook code you own."
lang: en
json-ld: |
  {
    "@context": "https://schema.org",
    "@graph": [
      {
        "@type": "WebSite",
        "@id": "https://prerenderlovable.com/#website",
        "url": "https://prerenderlovable.com",
        "name": "The Lovable Field Guide",
        "description": "A working journal on Lovable SEO: getting apps built with Lovable indexed, ranked and cited, plus the migration and integration work that comes with it.",
        "publisher": {
          "@id": "https://prerenderlovable.com/#organization"
        },
        "inLanguage": "en"
      },
      {
        "@type": "Organization",
        "@id": "https://prerenderlovable.com/#organization",
        "name": "The Lovable Field Guide",
        "url": "https://prerenderlovable.com"
      },
      {
        "@type": "BlogPosting",
        "@id": "https://prerenderlovable.com/blog/lovable-stripe-payments#article",
        "headline": "Lovable Stripe integration vs built-in payments",
        "description": "Lovable Stripe is set up through prompts, and it's a different thing from Lovable's built-in payments. How to choose, plus the webhook code you own.",
        "datePublished": "2026-09-11T00:00:00.000Z",
        "dateModified": "2026-09-11T00:00:00.000Z",
        "inLanguage": "en",
        "mainEntityOfPage": {
          "@type": "WebPage",
          "@id": "https://prerenderlovable.com/blog/lovable-stripe-payments"
        },
        "author": {
          "@type": "Organization",
          "name": "The Lovable Field Guide",
          "url": "https://prerenderlovable.com"
        },
        "publisher": {
          "@id": "https://prerenderlovable.com/#organization"
        },
        "keywords": "lovable stripe, lovable stripe integration, lovable payments, lovable stripe webhook, lovable paddle merchant of record",
        "articleSection": "Integrations"
      },
      {
        "@type": "BreadcrumbList",
        "@id": "https://prerenderlovable.com/blog/lovable-stripe-payments#breadcrumbs",
        "itemListElement": [
          {
            "@type": "ListItem",
            "position": 1,
            "name": "Home",
            "item": "https://prerenderlovable.com/"
          },
          {
            "@type": "ListItem",
            "position": 2,
            "name": "Integrations",
            "item": "https://prerenderlovable.com/integrations"
          },
          {
            "@type": "ListItem",
            "position": 3,
            "name": "Lovable Stripe integration vs built-in payments",
            "item": "https://prerenderlovable.com/blog/lovable-stripe-payments"
          }
        ]
      },
      {
        "@type": "FAQPage",
        "@id": "https://prerenderlovable.com/blog/lovable-stripe-payments#faq",
        "mainEntity": [
          {
            "@type": "Question",
            "name": "Is Stripe a one-click integration in Lovable?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "No. Lovable's Stripe documentation describes a prompt-driven flow, not a toggle: you store your Stripe secret key as a backend secret and Lovable generates edge functions that call Stripe with it. That is different from Lovable's separate built-in payments product, where Lovable handles account creation, connection and the technical setup for you using Paddle or Stripe."
            }
          },
          {
            "@type": "Question",
            "name": "Where does my Stripe secret key get stored in a Lovable project?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "As a backend secret named STRIPE_SECRET_KEY, submitted through a dedicated form rather than pasted into chat or into app code. On a Lovable Cloud project it appears in the Secrets section with a Lovable badge. On a project connected to your own Supabase, it syncs to the edge function secrets in your Supabase dashboard."
            }
          },
          {
            "@type": "Question",
            "name": "Does Lovable set up Stripe webhooks for me?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "Not by default. Per Lovable's docs, webhooks are optional and the generated app polls Stripe for payment status instead. If you want webhooks, Lovable will write the handler function, but you configure the event destination and the signing secret in the Stripe dashboard yourself."
            }
          },
          {
            "@type": "Question",
            "name": "Can I use my own Stripe account and Lovable's built-in payments at the same time?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "No. Lovable's docs state that you cannot use your own Stripe account together with built-in payments, and that a project has one payment provider. Built-in payments also require a paid Lovable plan, while bring-your-own-Stripe works on the free plan."
            }
          },
          {
            "@type": "Question",
            "name": "What does Lovable's built-in payments cost with Paddle?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "Lovable's payments documentation gives Paddle as 5.0% + 50c per transaction, with microtransactions under $10 charged at a flat 10% instead. Lovable states that using a provider through Lovable costs the same as going direct: you pay the provider's rate with no Lovable markup."
            }
          },
          {
            "@type": "Question",
            "name": "How does Lovable know whether to use Stripe test mode or live mode?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "Purely from the key prefix. A key beginning sk_test_ or rk_test_ puts you in Stripe's test environment; a live key puts you in production. There is no separate environment switch to forget, but it also means swapping the secret is the entire cutover, so verify it deliberately."
            }
          }
        ]
      }
    ]
  }
---

[Skip to content](#main)

[Lovable  Field Guide ](/)

[SEO](/seo)[Migration](/migration)[Integrations](/integrations)[All posts](/blog)

[Start here](/blog/lovable-seo)

[SEO](/seo)[Migration](/migration)[Integrations](/integrations)[All posts](/blog)

1.  [Home](/)
2.  /  [Integrations](/integrations)
3.  /  Lovable Stripe integration vs built-in payments 

# Taking payments in a Lovable app: Stripe or built-in payments

Lovable Stripe is set up through prompts, and it's a different thing from Lovable's built-in payments. How to choose, plus the webhook code you own.

Published September 11, 2026 · 12 min read 

Lovable has two separate payment stories, and they are not the same product. You either bring your own Stripe account, which Lovable sets up through prompts, or you use Lovable’s built-in payments, where Lovable handles account creation, connection and technical setup with Paddle or Stripe. Per Lovable’s docs you cannot run both on one project, so this is a decision to make before you write any checkout code.

Below: what each path does, how to choose between a merchant of record and direct Stripe, and then the part neither handles for you: the server-side correctness work that stops your app handing out paid plans for free.

Bring your own Stripe

Lovable built-in payments

How it’s wired

You store a key, Lovable generates backend functions that call Stripe

Lovable handles account creation, connection and technical setup

Providers

Stripe

Paddle or Stripe (one per project)

Lovable plan needed

Works on Free

Paid plan required

Who is the merchant

You

Paddle as full merchant of record; Stripe optionally via Managed Payments

Tax and invoicing

Yours to solve

Handled by Paddle as MoR

Headline rate

Your own Stripe pricing

Paddle: 5.0% + 50c, or flat 10% on microtransactions under $10

Control over the flow

Total: it’s your code

Lovable’s setup

The plan gating, provider list and Paddle rates in that table come from Lovable’s own documentation as of September 2026. For the wider picture of how Lovable wires anything external, the [integrations overview](/blog/lovable-integrations) covers the six connector types; Stripe-with-your-own-key is effectively the “any API” path with generated backend code.

## Path 1: bring your own Stripe account[#](#path-1-bring-your-own-stripe-account)

This is the one most people mean by “lovable stripe”, and the one most often described wrongly. There is no integration toggle. You describe the payment flow in chat, and Lovable writes backend functions that call the Stripe API with a key you stored separately.

### Where the key lives[#](#where-the-key-lives)

The key goes in as a backend secret named `STRIPE_SECRET_KEY`, submitted through a dedicated form. Lovable’s docs are explicit that it never lands in app code and never goes through chat. Where you find it afterwards depends on your backend:

-   **Lovable Cloud project**: it shows up in the Secrets section with a Lovable badge.
-   **Your own Supabase project**: it syncs to the edge function secrets in your Supabase dashboard.

That difference is one of the practical edges of the [Cloud versus your own Supabase decision](/blog/lovable-cloud-vs-supabase): with Cloud the key is managed inside Lovable’s editor; with Supabase it sits in infrastructure you own and can rotate without opening Lovable.

Test versus live is decided entirely by that key’s prefix. `sk_test_` or `rk_test_` means Stripe’s test environment; an `sk_live_` or `rk_live_` key is real money. No separate mode switch exists to forget about, which cuts both ways, since swapping the secret _is_ the whole cutover.

### What Lovable generates[#](#what-lovable-generates)

Per the Stripe integration docs, Lovable will build checkout functions and buy buttons, subscription checkout and status checks, customer portal integration, product and price creation inside your Stripe account (with your approval), and subscription matching by user email.

Matching subscriptions by email is fragile

Email is a mutable, user-controlled field. If a customer changes their account email, or pays with a different address than the one they signed up with, an email-keyed lookup quietly stops finding their subscription. Ask for the Stripe customer ID to be stored on the user row and keyed off that instead, with email as a fallback only. One line of prompt now, or a support queue later.

### The limits worth knowing before you start[#](#the-limits-worth-knowing-before-you-start)

Three from the docs. The last two bite during development; the first one bites in production:

1.  **Webhooks are optional and off by default.** The generated app polls Stripe for payment status instead. More on why that matters below.
2.  **The in-Lovable payments view is read-only.** Stripe’s dashboard remains the place you actually operate the business.
3.  **The customer portal doesn’t work inside the preview panel.** Test it on the published URL, in a real browser tab, or you’ll spend an afternoon debugging a sandbox restriction.

And, from the same page: you cannot use your own Stripe account and Lovable’s built-in payments at once.

## Path 2: Lovable’s built-in payments[#](#path-2-lovables-built-in-payments)

The second product is a different thing entirely: Lovable handles account creation, connection and technical setup, and recommends Paddle or Stripe, or offers both. The documented terms, as of September 2026:

-   **Paddle acts as a full merchant of record**, handling tax, compliance and invoicing, at **5.0% + 50c per transaction**. Microtransactions under $10 are charged differently: a flat **10%**.
-   **Stripe offers optional merchant-of-record service** through Managed Payments.
-   **Using a provider through Lovable costs the same as going direct.** There’s no Lovable markup layered on the provider’s rate.
-   Built-in payments need a **paid Lovable plan**. Bring-your-own-Stripe works on Free.

The constraints are specific: **digital products only** (physical goods route through the Shopify integration), **one payment provider per project**, **one subscription per user per environment** by default, and (the one that catches teams) **projects with payments cannot be remixed or forked.** If your workflow depends on remixing to spin up a variant or a client copy, adding payments closes that door.

## Merchant of record, or direct Stripe?[#](#merchant-of-record-or-direct-stripe)

A merchant of record is a different legal entity selling your product. Paddle becomes the seller on the customer’s statement and on the invoice, which means Paddle owes the VAT, files the returns, and absorbs the registration surface.

Compare that to what direct Stripe leaves on your desk. Stripe Tax calculates and collects the right amount, but calculation is the easy half. You still register wherever you cross a threshold and file on each jurisdiction’s schedule: an OSS registration and quarterly filings in the EU, economic nexus tracked state by state in the US. Neither is hard; both are ongoing, and both fail quietly until they fail expensively.

**Take the merchant-of-record deal when:**

-   You sell digital products internationally and have no tax function.
-   Your customers are consumers across many countries, so VAT applies from the first sale rather than after a threshold.
-   The 5.0% + 50c is comfortably inside your margin and you’d rather pay it than own the compliance calendar.

**Go direct on Stripe when:**

-   You sell B2B, where reverse charge means you often aren’t collecting VAT at all.
-   Your volume makes the spread real money. Two points of difference on $50k a month is $1,000 a month you are buying compliance with.
-   You need Stripe primitives an MoR layer doesn’t expose: usage-based billing, Connect, custom dunning, metered invoicing, or direct access to the raw event stream.
-   Your average order value is low. A 50c fixed component on a $4 sale is brutal; note that Lovable’s docs put sub-$10 transactions on the flat 10% instead, which is the more honest rate at that size.

Switching later is not free

Moving from an MoR to direct Stripe leaves your existing subscriptions at the old provider. There is no transfer button for someone else’s customer relationships and stored payment methods, in practice you run both for a renewal cycle and migrate people as they churn or re-subscribe. Decide as if it’s permanent, because for your first cohort it is.

## The five things you have to get right yourself[#](#the-five-things-you-have-to-get-right-yourself)

Neither path makes your billing correct. Generated code gets you a working checkout; these five get you one you can trust with other people’s money. All of it applies whether the code sits in a Supabase edge function or, on the TanStack Start stack Lovable made the default for new projects on 13 May 2026, in a server function inside the app: the shape differs, the rules don’t.

One thing about the snippets below: they are written in the Deno edge-function shape, because that is what Lovable generates on Cloud and on your own Supabase. `Deno.env.get()` does not exist on the TanStack Start stack. That code runs on Cloudflare Workers, where secrets arrive as request-time bindings rather than a process environment, so read `STRIPE_SECRET_KEY` off the env your server function is handed instead. Everything else below is identical on both stacks.

### 1\. The client never names the price[#](#1-the-client-never-names-the-price)

If a browser can send an amount to your backend, someone will send a different one. The server picks the price; the client picks a product _key_ the server resolves.

```
// Server-side only. Depends on: the Stripe SDK import Lovable generated in this
// file, STRIPE_SECRET_KEY as a backend secret, a SITE base URL, and `user`
// resolved server-side from the verified session: never from the request body.
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
const SITE = Deno.env.get('SITE_URL')!

// requireUser is yours: it verifies the request's auth token and returns the
// matching row. If the user identity comes out of req.json(), you have built
// the exact hole this section exists to close.
const user = await requireUser(req)
if (!user) return new Response('unauthorized', { status: 401 })

// The allow-list IS the pricing logic. Nothing else may set an amount.
const PLANS = {
  pro_monthly: 'price_1XXXXXXXXXXXXXXXXXXXXXXX',
  pro_yearly:  'price_1YYYYYYYYYYYYYYYYYYYYYY',
} as const

const { plan } = await req.json()
// Own properties only, and a string. A plain `PLANS[plan]` lookup happily
// returns an inherited function for plan = "constructor" or "toString".
if (!Object.prototype.hasOwnProperty.call(PLANS, plan) ||
    typeof PLANS[plan as keyof typeof PLANS] !== 'string') {
  return new Response('unknown plan', { status: 400 })
}
const priceId = PLANS[plan as keyof typeof PLANS]

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: priceId, quantity: 1 }],
  customer: user.stripe_customer_id ?? undefined,
  client_reference_id: user.id,        // survives into the webhook
  metadata: { user_id: user.id },      // so does this
  success_url: `${SITE}/welcome?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${SITE}/pricing`,
})
```

`client_reference_id` and `metadata` both come back on the webhook event, which is how you tie a Stripe payment to a row in your database without guessing from an email address. And if you find yourself building a `price_data` object out of request input, stop: that’s the same vulnerability wearing a Stripe-shaped hat.

### 2\. The client never asserts entitlement[#](#2-the-client-never-asserts-entitlement)

The second half of the same rule. A paid-feature check must read your own database, on the server, on every request that matters. `localStorage.isPro`, a claim in a JWT you minted before checkout, or a React state flag set on the success page are decorations: fine for hiding an upsell, useless as a gate. Generated code often gets this right in the obvious place, the pricing page, and wrong in the interesting one: the API route that does the expensive thing. Audit the second.

### 3\. Webhooks are the source of truth[#](#3-webhooks-are-the-source-of-truth)

Lovable’s default is polling: after checkout the app asks Stripe whether the session is paid. For a one-off purchase where the user waits on the success page that’s genuinely fine, and it’s why Lovable can ship a working flow without you configuring anything in Stripe.

It stops being fine the moment something happens without a browser attached. A customer closes the tab before redirect. A bank debit settles two days later. A subscription renews at 4am on day 31, or a card is declined on that renewal. None of those produce a page view, so a polling app never learns about them and your entitlement table drifts away from reality.

Per Lovable’s docs, if you want webhooks Lovable writes the handler, but **you** configure the event destination and signing secret in Stripe’s dashboard. That signing secret is a second secret you create yourself: Stripe hands it to you when you add the event destination, the name is your choice, and unlike `STRIPE_SECRET_KEY` it will not exist in the Secrets panel until you put it there. Whatever Lovable writes, verify the signature before reading a byte of the body:

```
// Verify, then dedupe, then act. In that order, always.
const sig = req.headers.get('stripe-signature')
const raw = await req.text()   // RAW body. Parsing first breaks the signature.

let event: Stripe.Event
try {
  // Use the async variant on Deno / Workers runtimes: the sync one wants
  // Node's crypto and will throw at the edge.
  event = await stripe.webhooks.constructEventAsync(
    raw, sig!, Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
  )
} catch {
  return new Response('invalid signature', { status: 400 })
}
```

An unverified webhook endpoint is an unauthenticated public API that grants subscriptions: anyone who finds the URL can POST themselves a lifetime plan. Verification isn’t optional hardening, it’s the whole gate.

### 4\. Make everything idempotent[#](#4-make-everything-idempotent)

Stripe delivers at least once, retries failures with exponential backoff over a period of days, and does not promise ordering. You will see the same event twice, and you will sometimes see `customer.subscription.updated` before the `checkout.session.completed` that created it. Both are normal.

Deduplicate on `event.id` with a unique constraint, and let the database do the work. The subtlety is _when_ a row counts as a duplicate: record receipt and completion separately, or a handler that throws halfway through leaves a row behind that makes Stripe’s retry look like a duplicate, and the event is lost for good.

```
// stripe_events(id text primary key, status text not null default 'processing',
//               received_at timestamptz default now())
// `db` is the service-role Supabase client: the anon client is blocked by RLS.
const { error } = await db.from('stripe_events').insert({ id: event.id })

if (error?.code === '23505') {                        // 23505 = unique_violation
  const { data: prior } = await db.from('stripe_events')
    .select('status').eq('id', event.id).single()
  if (prior?.status === 'handled') {
    return new Response('duplicate, already handled', { status: 200 })
  }
  // Still 'processing': an earlier attempt died mid-flight. Fall through and redo.
}

await handleEvent(event)   // convergent, so re-running it is safe
await db.from('stripe_events').update({ status: 'handled' }).eq('id', event.id)
```

Return 200 on a genuine duplicate. Returning an error makes Stripe retry an event you’ve already processed, which is how one duplicate becomes fifty.

Handle out-of-order delivery by making handlers _convergent_ rather than incremental: on any subscription event, re-read the subscription from Stripe and write its current state instead of applying a delta. That costs one API call and removes a whole category of bug.

On the outbound side, pass an idempotency key when you create anything chargeable, so a retried request doesn’t produce two subscriptions:

```
// `params` is the checkout-session object from the first snippet.
// Stripe idempotency keys expire after 24 hours, so a per-day stamp is the
// longest window that still does anything.
const dayStamp = new Date().toISOString().slice(0, 10)

await stripe.checkout.sessions.create(params, {
  idempotencyKey: `checkout:${user.id}:${plan}:${dayStamp}`,
})
```

### 5\. Handle the whole subscription lifecycle[#](#5-handle-the-whole-subscription-lifecycle)

A subscription is not a boolean. Stripe will hand you `incomplete`, `incomplete_expired`, `trialing`, `active`, `past_due`, `canceled`, `unpaid`, and `paused`, and code that only knows about `active` will get every edge case wrong in the direction of losing you customers.

Status

What happened

Grant access?

`incomplete`

First payment hasn’t succeeded yet (often 3-D Secure pending)

No

`incomplete_expired`

That first payment never completed in time

No

`trialing`

In trial, no payment taken yet

Yes

`active`

Paid and current

Yes

`past_due`

A renewal failed; Stripe is retrying

Yes, during a defined grace period

`unpaid`

Retries exhausted

No

`canceled`

Ended

No

`paused`

Trial ended without a payment method, under a pause behavior

No, usually

Three more things to handle. `cancel_at_period_end` means the customer has churned but stays entitled until the period ends: treating it as immediate cancellation is a refund request waiting to happen. A trial ending produces an `active` subscription and a real charge, so listen for `invoice.paid` and `invoice.payment_failed` too. And `current_period_end` moved off the subscription object onto its items in a newer Stripe API version, so the shape you get depends on the version your account and SDK are pinned to: check yours against Stripe’s API changelog before you write the read. Below that version it is still on the subscription; at or above it, read it from the item.

The minimum viable event set for a subscription product: `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`, `invoice.payment_failed`.

## Test mode, and what “tested” actually means[#](#test-mode-and-what-tested-actually-means)

The key prefix is the whole switch, so a test run is a test key plus Stripe’s test cards. `4242 4242 4242 4242` succeeds. `4000 0000 0000 9995` declines for insufficient funds. `4000 0025 0000 3155` forces a 3-D Secure challenge: the one that puts a subscription into `incomplete` and shows whether your success page copes.

Three things people skip:

1.  **Test-mode webhooks need their own endpoint and signing secret.** Test and live are separate worlds in the Stripe dashboard. A handler that works in test and silently 400s in production is usually a live signing secret nobody created.
2.  **Use Stripe’s test clocks for anything subscription-shaped.** Advancing a clock through a renewal and a failed payment takes minutes, and is the only way to hit `past_due` before a real customer does.
3.  **Re-test on the published URL.** Lovable’s docs note the customer portal doesn’t work in the preview panel. As of September 2026 the docs don’t say anything about redirect behavior there, so test your `success_url` and `cancel_url` on the published URL too rather than assuming the preview is representative.

## If you move your backend, your webhook endpoint moves too[#](#if-you-move-your-backend-your-webhook-endpoint-moves-too)

Webhook endpoints are configured in Stripe, pointing at a URL your backend serves. Stripe has no idea you migrated. Change where that URL resolves and events keep flowing to the old address until you notice: silently, because your app doesn’t error, it just stops learning about renewals.

This bites in every direction: moving from Lovable Cloud to your own Supabase, exporting and self-hosting, or changing your custom domain. Lovable’s docs are clear that [there’s no one-click migration between Cloud and your own Supabase](/blog/lovable-cloud-to-supabase) in either direction, and the full [migration guide](/blog/lovable-migration) covers the wider inventory of things that don’t travel with your code. Payments-specific checklist:

1.  Stand up the new endpoint and verify it _before_ cutting over. Send a test event from the Stripe dashboard.
2.  Add the new endpoint alongside the old one and let both run for a day. Duplicate events are harmless once you have the `event.id` dedupe in place.
3.  Store the new endpoint’s signing secret in the new backend. It is not the same string as the old one, and re-create `STRIPE_SECRET_KEY` there too: secrets are not part of a code export.
4.  Watch Stripe’s webhook delivery log for failures on the old endpoint, then remove it.
5.  Check the Customer Portal return URL and your `success_url` / `cancel_url` values for the old domain.

## What to do next[#](#what-to-do-next)

1.  Decide the path first (your own Stripe, or built-in payments with an MoR) and write down why. You cannot run both, and unwinding means rebuilding.
2.  Grep the generated backend code for anywhere an amount or price comes from the request body. Fix those first.
3.  Find every server route that gates a paid feature and confirm it reads entitlement from your database. A flag the client sends can be faked.
4.  If you sell subscriptions, turn webhooks on: ask Lovable for the handler, configure the event destination and signing secret in Stripe yourself, and add the `event.id` dedupe table in the same pass.
5.  Run a test clock through trial end, renewal and a failed payment. Better to find `past_due` unhandled here than in a support ticket.

Found something wrong or out of date? Lovable changes fast and we'd rather fix a guide than let it rot.

On this page

-   [Path 1: bring your own Stripe account](#path-1-bring-your-own-stripe-account)
-   [Where the key lives](#where-the-key-lives)
-   [What Lovable generates](#what-lovable-generates)
-   [The limits worth knowing before you start](#the-limits-worth-knowing-before-you-start)
-   [Path 2: Lovable’s built-in payments](#path-2-lovables-built-in-payments)
-   [Merchant of record, or direct Stripe?](#merchant-of-record-or-direct-stripe)
-   [The five things you have to get right yourself](#the-five-things-you-have-to-get-right-yourself)
-   [1\. The client never names the price](#1-the-client-never-names-the-price)
-   [2\. The client never asserts entitlement](#2-the-client-never-asserts-entitlement)
-   [3\. Webhooks are the source of truth](#3-webhooks-are-the-source-of-truth)
-   [4\. Make everything idempotent](#4-make-everything-idempotent)
-   [5\. Handle the whole subscription lifecycle](#5-handle-the-whole-subscription-lifecycle)
-   [Test mode, and what “tested” actually means](#test-mode-and-what-tested-actually-means)
-   [If you move your backend, your webhook endpoint moves too](#if-you-move-your-backend-your-webhook-endpoint-moves-too)
-   [What to do next](#what-to-do-next)

[More on Integrations](/integrations)

-   [Lovable integrations: the complete map](/blog/lovable-integrations)
-   [Adding a contact form to a Lovable app](/blog/lovable-contact-form)
-   [Build Lovable pages with Claude Code or Codex](/blog/lovable-with-claude-code-and-codex)
-   [How to spend fewer credits on Lovable](/blog/save-lovable-credits)
-   [Lovable MCP and chat connectors, explained](/blog/lovable-mcp-servers)
-   [Lovable Stripe integration vs built-in payments](/blog/lovable-stripe-payments)
-   [Lovable vs Cursor: which to use when](/blog/lovable-vs-cursor)
-   [Setting up Resend in a Lovable app](/blog/lovable-resend-email-setup)

## Frequently asked questions

Is Stripe a one-click integration in Lovable?

No. Lovable's Stripe documentation describes a prompt-driven flow, not a toggle: you store your Stripe secret key as a backend secret and Lovable generates edge functions that call Stripe with it. That is different from Lovable's separate built-in payments product, where Lovable handles account creation, connection and the technical setup for you using Paddle or Stripe.

Where does my Stripe secret key get stored in a Lovable project?

As a backend secret named STRIPE\_SECRET\_KEY, submitted through a dedicated form rather than pasted into chat or into app code. On a Lovable Cloud project it appears in the Secrets section with a Lovable badge. On a project connected to your own Supabase, it syncs to the edge function secrets in your Supabase dashboard.

Does Lovable set up Stripe webhooks for me?

Not by default. Per Lovable's docs, webhooks are optional and the generated app polls Stripe for payment status instead. If you want webhooks, Lovable will write the handler function, but you configure the event destination and the signing secret in the Stripe dashboard yourself.

Can I use my own Stripe account and Lovable's built-in payments at the same time?

No. Lovable's docs state that you cannot use your own Stripe account together with built-in payments, and that a project has one payment provider. Built-in payments also require a paid Lovable plan, while bring-your-own-Stripe works on the free plan.

What does Lovable's built-in payments cost with Paddle?

Lovable's payments documentation gives Paddle as 5.0% + 50c per transaction, with microtransactions under $10 charged at a flat 10% instead. Lovable states that using a provider through Lovable costs the same as going direct: you pay the provider's rate with no Lovable markup.

How does Lovable know whether to use Stripe test mode or live mode?

Purely from the key prefix. A key beginning sk\_test\_ or rk\_test\_ puts you in Stripe's test environment; a live key puts you in production. There is no separate environment switch to forget, but it also means swapping the secret is the entire cutover, so verify it deliberately.

Read next

## Keep going

-   ### [Lovable integrations: the complete map](/blog/lovable-integrations)
    
    Lovable integrations come in six connection types, and picking the wrong one is why wiring fails. What each type does, what exists, and how secrets flow.
    
-   ### [Lovable Cloud vs your own Supabase: how to choose](/blog/lovable-cloud-vs-supabase)
    
    Lovable Cloud vs Supabase is close to a one-way door. The documented tradeoffs, the credit pool that bills your hosting, and what switching later really costs.
    
-   ### [Migrating from Lovable Cloud to your own Supabase](/blog/lovable-cloud-to-supabase)
    
    There is no one-click Lovable Cloud to Supabase migration. The honest manual runbook: export, schema, RLS, edge functions, storage, secrets, auth and rollback.
    

The Lovable Field Guide

A working journal on Lovable SEO.

SEO

-   [SEO hub](/seo)

Migration

-   [Migration hub](/migration)

Integrations

-   [Integrations hub](/integrations)

© 2026 The Lovable Field Guide. Independent guides to shipping Lovable apps.

[RSS](/rss.xml) [Sitemap](/sitemap.xml) [About](/about)