Hire Lovable Xperts
Integrations & APIs

Lovable Stripe Works After Deploy but Fails in Preview

Your Stripe button works perfectly once the app is deployed, but in the Lovable preview the checkout fails, the webhook never fires, or you see a test/live key mismatch. The preview sandbox cannot receive Stripe webhooks and often runs different keys than production. Here is why preview cannot complete a Stripe round-trip, and how to test it correctly.

By Founder Name · Last verified: 2026-06-25

Why does Stripe only work after I deploy, not in preview?

The preview is a sandbox, and your Stripe configuration is split across two worlds. Stripe Checkout itself loads fine in preview because that is just a redirect to Stripe's hosted page. What fails is everything that depends on server state: the webhook that confirms the payment, the env-managed secret keys, and the redirect back to a stable URL. Preview and the deployed build run as separate environments, so a flow that needs both halves only completes after deploy.

Think of a Stripe purchase as a round-trip, not a single click. The browser redirects to Checkout (this works in preview), the card is charged at Stripe, and then Stripe must call back to a fixed server URL to tell your app the payment succeeded. That callback — the webhook — is the half that the preview environment cannot receive. So in preview the money can move but the activation never runs, which makes the integration look broken when it is really only half-connected.

There are five recurring reasons a Lovable Stripe flow behaves this way, and only one of them is an actual code bug. The rest are environment and configuration gaps that disappear the moment you point Stripe at a stable deployed URL with the right keys.

Why Stripe Fails in Lovable Preview but Works After Deploy
Preview SymptomRoot CauseFixable How
Checkout redirect works, account never upgradesWebhook is delivered to the production endpoint, not the preview URLTest via Stripe CLI forwarding, or test on the deployed build
Webhook log shows 400 on every deliveryTest/live key and signing-secret mismatch in the environmentMatch sk_ and whsec_ to the same Stripe mode
Works in preview, fails live (or vice versa)Different keys in editor sandbox vs deployed hostSet the same mode's keys in the production host and redeploy
Stripe cannot reach the endpoint at allPreview URL is ephemeral and may sit behind authRegister your stable deployed domain as the endpoint
Account upgrades on success page but not reliablyActivation runs client-side on success_url, not in the webhookMove activation into a signature-verified webhook
SubtleCryptoProvider cannot be used in a synchronous contextSynchronous constructEvent used inside a Deno edge functionSwitch to constructEventAsync

Related: if the webhook is configured but still not firing, start here · if the charge clears but the subscription never activates

Why can't the Lovable preview receive a Stripe webhook?

A Stripe webhook is an HTTP POST that Stripe sends to a fixed, public URL on your server after a payment. The Lovable preview is an ephemeral sandbox: its URL changes, it may sit behind auth, and it is not the endpoint you registered in Stripe. So Stripe delivers checkout.session.completed to your production endpoint, never to the preview, and the preview-side database never sees the upgrade. There is no preview webhook to fire.

Stripe only knows about the endpoint URL you registered under Developers, then Webhooks. That URL has to be stable and publicly reachable, because Stripe POSTs to it from its own servers and retries on failure. A preview URL on lovable.app is none of those things — it is generated for an editor session, it can change, and it is not the address Stripe holds. So even a perfectly written handler will never be invoked while you sit in preview.

This is why the flow appears to work the instant you deploy: the deployed build has a stable domain that matches the registered endpoint, so Stripe's callback finally lands somewhere your app is listening. Nothing about your code changed — the delivery target did.

A total absence of events in the Stripe webhook log usually means Stripe is delivering to your production endpoint while you are watching the preview. Check the deployed build's logs, not the preview, when verifying a webhook reached your server.

Is a test key vs live key mismatch why my payments fail?

Yes, and this is the most common reason live payments fail after a clean test. Stripe has two parallel modes with separate keys: sk_test_ and a test whsec_ for test mode, sk_live_ and a live whsec_ for live mode. Events from one mode are signed with that mode's secret. If production runs test keys, every real live event fails signature verification with a 400, and nothing activates even though the card was charged.

The trap is that each half of the pair must match the same mode. A common Lovable misconfiguration is a live secret key paired with a test webhook secret, or vice versa — the charge succeeds but the webhook returns 400 because the signing secret belongs to the other mode. The Stripe dashboard webhook log will show the 400, which is your fastest confirmation that this is a mode mismatch and not a code bug.

Match them deliberately: in test mode use sk_test_ together with the signing secret from your test-mode endpoint; in live mode use sk_live_ together with the signing secret from your live-mode endpoint. Set both values in the same environment, then redeploy. Validate test mode first, and only promote to live keys once the test-mode round-trip is clean.

What NOT to do: do not mix a sk_live_ key with a test whsec_, and do not copy the editor sandbox keys and assume they exist in the deployed host. Each Stripe mode has its own key and its own signing secret, and each environment needs both set explicitly.

How do I test the Stripe flow correctly without deploying every time?

Use Stripe test-mode keys, point the webhook at your deployed build's stable URL, and use the Stripe CLI to forward events to a local or preview run when you need fast iteration. Preview is for UI; the deployed test environment is for the full charge-to-activation round-trip. Never validate a payment flow with live charges — run Stripe's test card against test keys until the round-trip is clean, then promote to live.

  1. Set test-mode keys in your environment: sk_test_ for STRIPE_SECRET_KEY and the test endpoint's whsec_ for STRIPE_WEBHOOK_SECRET.
  2. Register your deployed build's stable domain as the webhook endpoint in Stripe under Developers, then Webhooks — never the lovable.app preview URL.
  3. Run a real checkout using Stripe test card 4242 4242 4242 4242, any future expiry, and any CVC.
  4. In Stripe, open Developers, then Webhooks, and confirm the checkout.session.completed delivery returned 200.
  5. Open your database and confirm the user's plan row actually flipped to paid with the customer and subscription IDs stored.
  6. Only after a clean test-mode round-trip, swap to sk_live_ and the live endpoint's whsec_ in production and redeploy.

Related: stop re-prompting Fix and burning credits on this

How do I watch the webhook hit my handler without redeploying?

The Stripe CLI lets you forward live webhook deliveries to any local URL, so you can watch checkout.session.completed hit your handler in real time without deploying. Run stripe listen, point it at your function, and trigger a test checkout. It prints each event, the signing secret to use, and the HTTP status your handler returns — turning the invisible preview gap into a live, readable log on your own machine.

The forwarding command looks like this:

# Forward Stripe events to a locally running edge function stripe login stripe listen --forward-to http://localhost:54321/functions/v1/stripe-webhook # In a second terminal, fire a real test event stripe trigger checkout.session.completed

The crucial detail: stripe listen prints its own webhook signing secret, for example whsec_1a2b3c..., and that is the secret your handler must verify against while the CLI is forwarding. It is NOT the same as your dashboard endpoint's whsec_. Set STRIPE_WEBHOOK_SECRET to the value the CLI prints for local testing, then switch back to the dashboard endpoint's secret when you deploy. Mixing these two is a frequent cause of a 400 that looks like a code bug.

The Stripe CLI is the supported way to test a webhook against code that is not yet on a public URL. It removes the need to deploy on every change, which is the real friction behind testing payments in a preview-only workflow.

Why do my Stripe keys exist in preview but vanish after deploy?

Because the keys and the webhook secret live in environment variables, and preview, the deployed build, and your local machine are three separate environments. A key set in the Lovable editor sandbox does not automatically exist in the deployed host, and a webhook secret printed by the Stripe CLI is different from your dashboard endpoint's secret. This is the Vanishing Env-Var pattern: the value exists in one environment and is silently absent in the next.

When Lovable generates a Stripe integration, the secrets you paste into the editor are scoped to that sandbox. The deployed build reads from its own host environment, and your local CLI run reads from yet another. Each one needs STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET set explicitly. Miss one and the symptom is exactly the confusing split this article describes: it works here, it fails there, and nothing in the code is wrong.

The fix is mechanical but easy to skip: set both Stripe secrets in every environment that runs the handler — the deployed host's settings panel for production, and the CLI's printed secret for local forwarding — then redeploy. After that, verify in the deployed build's logs rather than assuming the preview reflects production.

Related: why env vars disappear when you deploy a Lovable app · keep Stripe secrets out of the browser entirely

When should I hand the Stripe integration to a senior engineer?

Hand it over once the Stripe dashboard shows successful payments but your accounts still do not provision, or once you have spent more than a couple of prompts re-generating the same checkout handler. Payment bugs are the one place where guessing costs real money — every failed activation is a customer who paid and got nothing. A senior engineer traces the event from Stripe delivery to the exact database write.

A specialist confirms the endpoint URL, matches the keys and signing secret to the correct mode, verifies the signature with the async crypto path on Deno, and proves the activation write lands in the right user row — then runs a full test-mode round-trip plus the Stripe CLI to leave you with a readable, reproducible flow. We also resend any failed events so customers who paid during the broken window get provisioned without manual database edits.

Do not iterate on payment code by re-prompting Lovable. Re-prompting a charge handler risks stacking a second silent failure on top of the first — the Bug Doom Loop applied to the one part of your app where a mistake costs real money.

Frequently asked questions

Why does my Stripe checkout work in Lovable preview but the account never upgrades?
The Stripe Checkout redirect itself works in preview — clicking the button sends you to Stripe's hosted page like normal. What does not work is the server-side confirmation. Stripe sends the checkout.session.completed webhook to the fixed production endpoint you registered, not to the ephemeral preview URL. So the payment clears, but the preview app never receives the event that flips the account to paid. The full loop only closes on the deployed build.
Why can't the Lovable preview receive a Stripe webhook at all?
A Stripe webhook must reach a fixed, public URL. The Lovable preview URL is ephemeral, can change between sessions, and is not the endpoint you registered in Stripe — so Stripe has nowhere to deliver the event in preview. The deployed build has a stable domain that matches your registered endpoint. That single difference is why activation completes after deploy and silently never completes in preview.
How does a test key vs live key mismatch break my Stripe payments?
Stripe runs two completely separate modes — test and live — each with its own secret key and its own webhook signing secret. A test event is signed with the test whsec_; a live event with the live whsec_. If your production environment holds test keys, every real payment fails signature verification with a 400 and never activates. Confirm both STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are the live-mode values in your deployed host.
How do I test a Stripe webhook without deploying on every change?
Use the Stripe CLI. Run stripe listen --forward-to followed by your function URL, and it forwards real webhook deliveries to any target — local or preview — while printing each event and the exact signing secret to use. This turns the invisible preview gap into a readable log, so you can watch checkout.session.completed reach your handler and see the status code it returns without deploying first.
Can I test the payment flow with a real card to make sure it works?
No — use test-mode keys and Stripe's test card, 4242 4242 4242 4242 with any future expiry and any CVC. Live charges to validate configuration cost real money and create real refunds. Run the full charge-to-activation round-trip against sk_test_ and the test whsec_ until it is clean, then promote to live keys only in your production host. Never debug a payment flow with live cards.
Why do my Stripe keys work in preview but disappear after I deploy?
Almost certainly a key or secret that exists in one environment but not the next. A key set in the Lovable editor sandbox is not automatically present in the deployed host, and the Stripe CLI prints a different signing secret than your dashboard endpoint uses. This is the Vanishing Env-Var pattern. Set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET explicitly in your production host's settings panel and redeploy.
Can I make Stripe fully work inside the Lovable preview itself?
It can, but not in the preview sandbox — you cannot register the ephemeral preview URL as a stable Stripe endpoint, and preview may sit behind auth Stripe cannot reach. The supported pattern is to test with the Stripe CLI forwarding events to a local or preview run, then register your real deployed domain as the production endpoint. Treat preview as UI-only for payments.
Should I activate the subscription on the success page so it works in preview?
Yes. The success_url redirect is just a page Stripe shows after payment; it is not a guarantee. If the user closes the tab or the redirect is blocked, any activation code on that page never runs even though the charge cleared. Webhooks are the only signal Stripe retries until your server confirms. Move activation into a signature-verified webhook and keep the success page display-only.
How fast can you fix a Lovable Stripe integration that only works after deploy?
If the dashboard shows successful payments but accounts still are not provisioning, or if you have re-prompted the same checkout handler more than a couple of times, hand it over. Payment code is the one place where a wrong guess costs real money and real customer trust. A senior engineer traces the event from Stripe delivery through verification to the exact database write, then resends the backlog so anyone who paid during the broken window gets provisioned.
The checkout redirect works but the webhook never fires in preview — what is happening?
The redirect to Stripe Checkout works because it is a plain browser navigation to Stripe's hosted page. The webhook fails because Stripe must POST the confirmation to a fixed, registered URL — and the preview URL is ephemeral and not that endpoint. So the charge clears at Stripe but the event never reaches your preview app, and the upgrade never runs. The loop only closes on your stable deployed domain.

Talk to a senior engineer — not a salesperson.

Book a free 30-minute audit call. We'll diagnose what's wrong and tell you exactly what it costs to fix.

Book a free audit call