Vibe Code Rescue

Stripe payments are not working in your Lovable app

You built an app with Lovable, wired up Stripe, and now something in the payment flow is broken. The checkout button does nothing. Or checkout opens but shows "Invalid API Key provided". Or the payment goes through in Stripe, the money shows up, and your app still treats the user as unpaid. These are five different problems with five different fixes, and the error you see tells you which one you have.

How Stripe is wired in a Lovable app

Lovable does not call Stripe from your frontend. It generates a Supabase Edge Function (usually named something like create-checkout or create-payment) that holds your Stripe secret key and creates the checkout session. Your app calls that function, gets a checkout URL back, and sends the user to Stripe. A second edge function (often stripe-webhook) listens for Stripe to say "payment succeeded" and updates your database. So a broken payment flow can live in four places: the keys, the edge function, the webhook, or the URLs between them. Here is how to find which.

Diagnose it in two minutes

  1. Open your app, press F12 for devtools, open the Console and Network tabs, and click the checkout button. If a request to functions/v1/create-checkout (or similar) turns red with a 500, the edge function is failing. If it is blocked with a CORS error, that is cause 5. If nothing happens at all in the Network tab, the button is not wired up, which is a frontend bug, not a Stripe problem.
  2. In the Supabase dashboard, go to Edge Functions, click the function, and open Logs. The real error message is here, not in your browser.
  3. If checkout works but the app never registers the payment, go to the Stripe dashboard, then Developers, then Webhooks, and look at the delivery attempts. Red 400s mean cause 3.

Cause 1: test keys and live keys are mixed up

Stripe gives you two full sets of keys. Test keys start with pk_test_ and sk_test_. Live keys start with pk_live_ and sk_live_. They are separate universes: a session created with a test secret key cannot be opened with a live publishable key, and test payments never show up in live mode. The classic symptoms: checkout worked fine in preview and broke on the published site (or vice versa), or checkout shows "Invalid API Key provided", or payments "succeed" but no money ever arrives because you are still in test mode and the customer used card 4242 4242 4242 4242.

Fix: pick one mode and make every key match. In the Stripe dashboard, use the test/live toggle to grab both keys from the same mode. Put the secret key in your Supabase edge function secrets (next cause shows where) and make sure any publishable key in the frontend is from the same mode. If you are going live, you also need to complete Stripe's account activation first, or the live keys will not work at all. Then republish the app so the frontend picks up the change.

Cause 2: the Stripe secret key never made it into Supabase

This is the most common one. The edge function code contains a line like Deno.env.get("STRIPE_SECRET_KEY"). That value is not in your code and not in Lovable. It lives in the Supabase dashboard, and if it was never set there, the function crashes the moment it runs. From the outside, the checkout button does nothing or shows a generic "Edge Function returned a non-2xx status code" error, and the Network tab shows a 500.

Confirm it: Supabase dashboard, Edge Functions, pick the function, open Logs. You will see something like "STRIPE_SECRET_KEY is not set", or a Stripe error saying no API key was provided, or "Invalid API Key provided: undefined".

Fix: in the Supabase dashboard go to Settings, then Edge Functions (or Edge Functions, then Secrets, depending on the dashboard version), and add a secret named exactly STRIPE_SECRET_KEY with your sk_test_ or sk_live_ value. The name must match what the code reads, character for character. Watch for pasted whitespace, a trailing space breaks the key. New secrets apply to new invocations, so just retry checkout after saving.

Cause 3: payment succeeds but the app never updates

The customer pays. Stripe shows the charge. Your app still says free plan, zero credits, not subscribed. This is a webhook problem. Your app does not find out about successful payments by magic: Stripe has to call your webhook edge function, and that call is either not configured or being rejected.

Confirm it: Stripe dashboard, Developers, then Webhooks. Two possibilities. First, there is no endpoint listed at all, so Stripe has nowhere to send the event. Second, the endpoint exists but the delivery attempts show 400 errors, usually with a body about signature verification failing ("No signatures found matching the expected signature for payload"). That means the STRIPE_WEBHOOK_SECRET in your Supabase secrets does not match this endpoint's signing secret. Each webhook endpoint has its own whsec_ secret, and test mode and live mode endpoints are separate, so it is easy to have the wrong one.

Fix: if there is no endpoint, add one pointing at your function URL, which looks like https://YOUR-PROJECT-REF.supabase.co/functions/v1/stripe-webhook, and subscribe it to the events the code handles (at minimum checkout.session.completed; for subscriptions, the code usually also wants the customer.subscription events, check what it listens for). Then click "Reveal" on that endpoint's signing secret and save it as STRIPE_WEBHOOK_SECRET in Supabase edge function secrets. If the endpoint exists but 400s, re-copy the signing secret from that exact endpoint into Supabase. Then use "Resend" on a failed delivery in the Stripe dashboard to replay it and confirm it now returns 200. One more check: the webhook function must be callable without a logged in user. In Supabase, that means JWT verification has to be disabled for that specific function (in the function's settings, or verify_jwt = false in the project's config.toml), otherwise Supabase rejects Stripe with a 401 before your code ever runs.

Cause 4: after paying, users land on localhost or the preview URL

Checkout itself works, but after paying the customer lands on a dead localhost page, the Lovable preview domain, or an old URL. The checkout session is created with a success_url and cancel_url, and the AI hard-coded them to whatever domain existed when it wrote the function. The payment is real (the webhook still fires), but the customer experience is broken and they will email you asking if they got charged.

Fix: open the edge function code and look at the success_url and cancel_url passed to the session create call. Replace any localhost or preview address with your published domain, or better, have the function build the URL from the request origin header so it works in both preview and production. Redeploy the function (in Lovable, ask it to make this exact change and republish).

Cause 5: CORS errors when calling the function from the published domain

The console shows something like "Access to fetch at ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present". Edge functions have to answer the browser's preflight OPTIONS request and send CORS headers on every response. Lovable's generated functions usually include this, but it breaks when the headers are only sent on the success path (so real errors surface as CORS instead of the actual message) or the allowed origin is pinned to an old domain.

Fix: the function should handle OPTIONS requests by returning the CORS headers immediately, and include Access-Control-Allow-Origin plus Access-Control-Allow-Headers (covering authorization, content-type, and the Supabase client headers) on every response, including error responses. If the origin is hard-coded, set it to your published domain or *. Note that a CORS error in the console sometimes masks cause 2: the function crashed before it could attach headers. Check the edge function logs before touching CORS code.

Do not do this

Do not paste your secret key into the frontend or into the Lovable chat to "make it work". Anything in the frontend bundle is public, and a leaked sk_live_ key lets anyone create charges and refunds against your account. The secret key belongs in Supabase edge function secrets and nowhere else. If it has ever been in your code or a public repo, roll it in the Stripe dashboard (Developers, then API keys, then roll key) and update the Supabase secret. And do not keep re-prompting the AI to rewrite the payment code: in four of the five causes above, the code is fine and the fix is a setting in the Stripe or Supabase dashboard.

Still stuck?

Run the instant diagnosis. Paste your public repo URL into the form and an automated clean-room check reports what is broken: install, build, render, and config, with the exact blocker named, in minutes, free. Stripe keys and webhook settings live in your Stripe and Supabase dashboards rather than your code, so for this one also paste the edge function log error and the webhook delivery status into the issue and I will look at it the same day. Private repo or zip? Email works too.

Get an instant free diagnosis

Or email me instead. Fixes with 24 hour turnaround start at $95. Prefer self-serve? The $5 instant diagnosis on Apify checks your repo privately, no public issue needed.