AI-coded apps

    Does Lovable.dev support Stripe integration safely?

    Stripe secret key handling inside a Lovable.dev project, with Supabase Edge Functions storing sk_live in a server-side secrets manager

    You shipped the first version of your app on Lovable.dev, paywalled a feature, and now Stripe sits at the center of how you make money. The question that should come before the first charge is whether the integration that the AI produced actually keeps the Stripe secret key out of the browser. The answer depends almost entirely on how the key was added.

    Short answer

    The short answer is yes, Lovable.dev supports Stripe integration safely when the Stripe secret key is added through Lovable's Add Secret panel and lives only inside a Supabase Edge Function. The publishable key (pk_live) belongs in the frontend; the secret key (sk_live or the safer rk_live restricted variant) never does. According to Stripe's best practices for managing secret API keys, publishable keys are safe to include in webpages and apps, and secret keys must stay in your server environment. Lovable's documented flow matches that rule when it is followed; the failure mode is the AI writing the secret directly into a component because someone pasted it into the chat.

    What you should know

    • Two keys, two homes. The Stripe publishable key (pk_live) is built for the browser; the secret key (sk_live) is built for the server. Mixing them up is the root of most leaks.
    • The chat is not a secrets vault. Pasting sk_live into the Lovable prompt window risks the value landing inside generated code that ships to the bundle.
    • Edge Functions are the safe surface. Stripe API calls in a Lovable project should live in Supabase Edge Functions, where the secret is read from Deno.env.get('STRIPE_SECRET_KEY').
    • Restricted keys beat secret keys. A Stripe restricted API key scoped to the minimum permissions limits the damage if the value ever leaks.
    • Webhooks need signature checks. The Stripe webhook secret has to be verified inside the Edge Function; a webhook endpoint without verification can be called by anyone.
    • The publishable key alone cannot charge the account. Per Stripe's API keys documentation, a publishable key cannot read customers, issue refunds, or list charges; that is why it is safe in the bundle.

    How does Lovable actually store the Stripe secret key?

    The short answer is that Lovable stores it in Supabase's Edge Function secrets manager, not in the frontend project files. When a user asks Lovable to add Stripe checkout, the assistant detects that a secret is needed and shows an Add Secret panel. The value typed into that panel is encrypted at rest by Supabase and exposed to Edge Functions through Deno.env.get. According to Supabase's documentation on Edge Function environment variables, those variables are scoped to the function runtime and never serialised into the React bundle.

    This matters because the Lovable frontend is a static build. Anything that reaches the React side ships to the browser, including any string that the AI hardcoded into a component. The Add Secret path keeps the key on the server side of the line. The chat path does not.

    The mistake reported most often on the Lovable subreddit and X is pasting the key directly into the prompt. The model can refuse, can mask, or can paste it verbatim into a generated component. Once the value is in the project source, it is one push away from the live preview URL.

    What does a safe Stripe wiring look like end to end?

    The pattern the official Lovable guidance points to is a three-layer split: publishable key in the browser, secret key in an Edge Function, webhook secret in a second Edge Function. Each layer has a specific job and a specific key.

    LayerRuns whereStripe key usedJob
    Checkout buttonBrowser (React)pk_live (publishable)Redirect to Checkout or confirm a PaymentIntent
    Charge functionSupabase Edge Functionsk_live or rk_live (server)Create the PaymentIntent or Checkout Session
    Webhook handlerSupabase Edge FunctionStripe webhook secretVerify and process events from Stripe

    The browser layer only ever knows the publishable key. It calls the charge function with a Supabase Authorization header that proves which user is checking out. The charge function reads sk_live from Deno.env.get, talks to Stripe, and returns the Checkout URL or the client secret. The webhook handler reads the raw body, verifies the Stripe-Signature header against the webhook secret, and updates the database when an event lands.

    Supabase's official Stripe webhooks example shows the verification code in full. The function calls stripe.webhooks.constructEventAsync(body, signature, webhookSecret) and exits on a thrown error. Skipping that call is the equivalent of leaving the door open.

    When does the sk_live key actually leak in Lovable projects?

    The honest answer is that the leak is almost always a human action, not a Lovable bug. Three patterns recur.

    First, the chat-pasted key. A user pastes the live secret key into the prompt, the model writes it into a component, the file is committed, and the bundle ships to the preview URL. Automated scanners on GitHub and on the public web pick up sk_live_ prefixes quickly. Stripe runs proactive detection and may deactivate the key on its side before the developer notices.

    Second, the public env file. A user creates a .env file in the project, adds VITE_STRIPE_SECRET_KEY=sk_live_..., and references it from the frontend. Vite inlines any variable prefixed with VITE_ into the client bundle by design. The variable name is unrelated to the protection; the prefix is what determines visibility.

    Third, the missing Edge Function. A user wires the Checkout call directly from the React component using fetch, with the secret key in an Authorization header on the client. The network tab in browser dev tools reveals the value on the first attempt. The fix is to move the call into a function and proxy the request.

    The takeaway: the Lovable runtime does not embed sk_live into the bundle on its own. The bundle leaks happen when the developer routes the key around the supported flow.

    How do Stripe restricted API keys reduce the risk further?

    The short answer is that a restricted API key (rk_live) gives the same code less power. According to Stripe's restricted API keys documentation, a restricted key carries only the permissions you assign per resource: None, Read, or Write. A key scoped to PaymentIntents Write and Customers Read can create charges and look up the customer, and that is the entire surface.

    Stripe's own guidance recommends restricted keys over unrestricted secret keys, especially when the key is going to an AI agent. That recommendation applies cleanly to Lovable, since the assistant generates code that calls Stripe on the developer's behalf. A leaked rk_live with a tight scope cannot list every charge in the account, cannot create refunds, and cannot pull customer payment methods. The blast radius shrinks from total account access to one resource family.

    The practical step is to open the Stripe Dashboard, create a restricted key with the smallest set of permissions the function needs, and paste that value into Lovable's Add Secret panel as STRIPE_SECRET_KEY. The function code does not change. The risk profile drops.

    What about the Stripe webhook secret in Lovable?

    The short answer is that the webhook secret is a separate value from the API key, and it has to be verified inside the Edge Function on every call. The webhook secret signs the payload Stripe sends to the endpoint; without verification, anyone who knows the URL can post arbitrary JSON and trigger downstream logic.

    The Supabase Edge Function pattern for this is small: read the raw body with await req.text(), read the signature with req.headers.get('Stripe-Signature'), call stripe.webhooks.constructEventAsync(body, signature, Deno.env.get('STRIPE_WEBHOOK_SECRET')), and let a thrown error return a 400. The pattern is the same one shown in the Supabase example linked above, and it is the same one Stripe documents in its server SDKs.

    Two edge cases trip developers up. The body must be the raw text, not the parsed JSON; parsing first changes the byte order and the signature check fails. The webhook secret in test mode is different from the one in live mode, so promoting an app from test to live requires updating the secret in Lovable's panel and pointing the Stripe Dashboard to the live endpoint.

    What to watch out for

    A few details cause friction in real Lovable and Stripe projects. None are dealbreakers, all are worth knowing.

    The AI can be persuasive about the wrong approach. If a prompt insists on putting the secret key into a React component, the model can comply. Treat any reply that includes sk_live_ or sk_test_ inside JSX as a failure that needs to be reverted before the next commit.

    The Supabase service role key is a separate hazard. It is not a Stripe key, but a leak has the same shape: full database access from the browser. The same Add Secret panel applies. The Lovable security pitfalls guide is explicit that secrets stored in frontend code are visible to users and should be considered compromised.

    Lovable's public preview URL is indexable. A bundle with a leaked key is not a secret leak inside a private staging environment; it is a public leak that crawlers and scanners can reach. Treat the preview URL like production for secret hygiene.

    Rotation is not a one-time exercise. Stripe recommends a documented rotation process so each key's location is known and replaceable on short notice. For a Lovable project this means knowing which function uses which secret name and being able to swap a value through the Add Secret panel without redeploying the frontend.

    The idea that the publishable key needs protecting is worth retiring. The publishable key is meant to be public. Restricting it, hiding it, or storing it as a secret achieves nothing and complicates onboarding for the next developer who touches the code.

    Key takeaways

    • Lovable's safe Stripe path puts pk_live in the browser, sk_live or rk_live in a Supabase Edge Function, and the webhook secret in a second function with signature verification on every call.
    • The bundle leak risk is almost always a developer action: pasting the secret into chat, prefixing an env variable with VITE_, or calling Stripe directly from a React component.
    • Restricted API keys are the safer default in any AI-generated codebase; scope them to the minimum resources the function needs and accept the operational cost of a separate key per service.
    • For builders who want an external read of their compiled mobile build before submission, including a check that no sk_live or sk_test strings made it into the binary, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS for no-code and vibe-coded apps.
    • Treat the preview URL like production for secret hygiene: the same scanners that watch GitHub watch public web bundles, and Stripe's own detection may revoke a leaked key before the developer sees the alert.
    • #lovable
    • #stripe
    • #api keys
    • #sk_live
    • #supabase edge functions
    • #vibe coding
    • #payments

    Frequently asked questions

    Is the Stripe publishable key safe to keep in my Lovable frontend?
    Yes. The publishable key starting with pk_live or pk_test is designed for client code. According to Stripe's API keys documentation, it can create tokens and confirm payments, and that is the limit of what it can do. It cannot read customers, issue refunds, or list charges. Lovable embeds the publishable key in the bundle by design, and that is the intended pattern.
    What goes wrong if I paste sk_live into a Lovable chat prompt?
    Lovable's security guide treats secrets embedded in frontend code as compromised the moment they reach the bundle. If the AI ends up writing the key into a component, an env file shipped to the browser, or a config object, automated scanners pick it up within hours. The safer flow is to use the Add Secret panel so the value is stored in Supabase Edge Function secrets and never sent to the client.
    Do I need to use a Stripe restricted API key instead of sk_live?
    It is the safer default. Stripe's restricted API keys documentation explicitly recommends restricted keys over unrestricted secret keys, especially for code written or operated by AI agents. A restricted key scoped to PaymentIntents write and Customers read limits the blast radius if the key ever leaks. Lovable accepts rk_live values in the same secrets panel as sk_live.
    Can a Lovable Edge Function verify Stripe webhooks correctly?
    Yes, when the webhook secret is stored as a Supabase Edge Function secret and the signature is checked using Stripe's library. Supabase's documented Stripe webhook example shows the pattern: read the Stripe-Signature header, call constructEventAsync with the raw request body, and reject events that fail verification. Skipping this step lets anyone hit the endpoint and trigger fake events.
    Does Lovable's hosted preview leak the secret key during development?
    Not on its own, provided the secret was added through the Add Secret panel rather than pasted into the chat. Secrets entered through the panel sit in Supabase's Edge Function environment, which the frontend cannot read. Secrets pasted into the chat may be echoed back into generated code, and any code that lands in the React build is downloadable from the preview URL by anyone.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free