Skip to content
Guide

Lovable Stripe: paid but account not upgraded

Stripe took the money, but your app never heard about it. Here is how to find where the message got lost, put affected customers right today, and stop it happening again.

Sources checked October 10, 202613 min read
A black payment card with a chip on a white background
Photo: Markus Winkler / Unsplash

As of October 10, 2026, when a customer pays through Stripe but a Lovable app still treats them as free, the payment almost always went through and the message telling your app about it got lost. The usual causes are an app that only upgrades people when they land back on its success page, which they can close; a webhook (Stripe's automatic message to your server) that is missing, points at the wrong address or is rejected because its signing secret is wrong; test-mode settings left behind after going live; and a database rule that quietly blocks the update. The fix: read Stripe's delivery log, repair the endpoint, correct affected customers from Stripe's records, and let a signature-checked webhook that ignores repeats decide who has paid.

This guide is for founders whose Lovable app takes payments and has started getting emails like "I paid but I'm still on the free plan", "I canceled and you charged me again" or "you charged me twice". It covers the causes in plain words, a diagnosis you can run yourself, how to fix customers already affected, and the durable fix. Platform facts come from the Lovable, Stripe and Supabase documentation listed under Sources. For first-time setup, see our Lovable Stripe integration guide.

Which Stripe setup does your Lovable app have?

Lovable offers two ways to take payments, and they fail in different places. Built-in payments, which Lovable's changelog introduced as Lovable payments in its April 24, 2026, entry, run on Stripe or Paddle. Lovable creates the provider account, registers webhook endpoints for the test and live environments, and keeps subscription data current. Connecting your own Stripe account uses an API key you supply. Lovable's documentation says that route does not use webhooks by default: the app asks Stripe directly whether the signed-in user has an active subscription. A project uses one or the other, not both.

Built-in paymentsYour own Stripe account
How the app hears about a paymentWebhooks Lovable registers on the provider account, one set per environmentA check with Stripe when users sign in and use the app; webhooks only if you ask for them
How a payment finds its userLovable recommends sign-in so purchases link to individual usersBy email address: the sign-in email must match the Stripe customer's email
Test and livePreview is test mode, the published app is live; products sync when you publishOne key at a time; price IDs differ between test and live

Why does a customer pay but stay on the free plan?

Stripe almost always did its job. Something between Stripe and your database did not. The causes we check:

  • The app only upgrades people on the way back. A customer who closes the tab before the success page loads never gets upgraded. Stripe's fulfillment guide says you cannot rely on the landing page alone, because a customer can pay and lose their connection before it loads, and that webhooks are required if you sell subscriptions.
  • The emails do not match. With your own Stripe account, Lovable matches subscriptions to users by email. A customer who paid with a work address but signs in with a personal one looks unpaid.
  • There is no webhook, or it points at the wrong address. With your own Stripe account, you create the event destination in Stripe yourself. With built-in payments, Lovable warns that deleting your app's endpoint silently stops payment events: purchases still succeed, but subscriptions never activate.
  • The webhook is rejected. Your server must check Stripe's signature using the endpoint's signing secret and the untouched request body. Stripe names the wrong secret as the most common cause of signature failures. On Supabase, edge functions require a valid token (a JWT) by default, and Stripe does not send one, so a webhook function left on that default is refused.
  • Test settings survived going live. Stripe keeps sandbox and live mode apart: separate keys, separate webhook endpoints, a different signing secret for each mode, and sandbox products and prices that do not work in live mode.
  • The database quietly refuses the write. If the update runs under a user-level key, the database's access rules (row-level security, RLS) can filter the row out. Supabase's documentation notes that this raises no error and simply matches zero rows.
  • The event is not handled. A Checkout payment arrives as checkout.session.completed, a renewal as invoice.paid, and plan changes and endings as customer.subscription.updated and customer.subscription.deleted. A handler that only knows one of them misses the rest.

Why doesn't canceling downgrade them, and why was someone charged twice?

Cancellations fail for the same reason: the app never hears about them. When a customer cancels at the end of their billing period, Stripe sends customer.subscription.updated straight away, then customer.subscription.deleted when the period ends. An app that only listens for checkout events never learns the subscription ended. Lovable's own advice is not to cut access the moment someone cancels, because they have paid to the end of the period. Stripe's advice is to revoke access when the status becomes canceled or unpaid.

A real double charge usually means two subscriptions: the customer started checkout twice, or paid again because the app still showed them as free. A Stripe setting under Checkout and Payment Links sends customers who already have an active subscription to the customer portal instead. It finds them by the customer record or the email address, and it only works while the portal's login link is enabled.

Repeated events cause a different problem. Stripe says an endpoint may occasionally receive the same event more than once. A handler that does not remember which events it has processed can add credits twice or send two receipts. That is not a second charge, but customers read it as one. Handling every event exactly once is called idempotency.

How do you find where the payment message got lost?

  1. 01Find the payment in StripeOpen the payment in the Stripe Dashboard, in live mode, not a sandbox. Compare the customer's email with the one they sign in with.
  2. 02Check a live endpoint existsIn Workbench, open the Webhooks tab. You should see a live destination pointing at your app. Built-in payments have two per environment: your app's endpoint, ending in ?env=live, and Lovable's monitoring endpoint. Leave both. No live endpoint means you have found the cause.
  3. 03Read the event deliveriesOpen the endpoint's Event deliveries tab. Each event shows Delivered, Pending or Failed; click one to see each attempt's HTTP status code, explained below.
  4. 04Confirm the event happenedWorkbench's Events tab lists recent events. Check this payment's event exists and the endpoint is set to receive that type.
  5. 05Read your backend logsOn Lovable Cloud, open More, then Cloud, then Logs, and look at function and edge logs. On your own Supabase project, open Edge Functions, select the webhook function and check Invocations and Logs.
  6. 06Check the customer's rowLook at the customer's plan in your database. A 200 delivery with no change points to an email mismatch, a blocked write or an ignored event.
What Event deliveries showsWhat it usually meansWhat to do
Delivered (200), customer still freeThe handler ran but changed nothing: wrong user matched, write blocked by RLS, or event type ignoredRead the function logs and the customer's row
400Often a failed signature check: the sandbox secret is still in use, or the body was altered before checkingStore the live endpoint's whsec_ secret and verify against the raw body
401 or 403The address refuses Stripe, often a Supabase function expecting a tokenLet the webhook function skip the token check and verify Stripe's signature instead
404The address is wrong or the function is not deployedFix the endpoint URL
302 or other 3xxA redirect, which Stripe counts as a failurePoint the endpoint at the final address
500 or timed outThe handler crashed or took too longRead the logs; reply 2xx first, work after

With the status code in hand, paste this into Lovable with the real details: "Stripe's event deliveries for our webhook show [status code] for [event type]. The function log says [paste]. Make the webhook function verify the Stripe signature against the raw request body using the live signing secret, not require a user login token, write with server-side credentials, record each Stripe event ID and skip repeats, and handle checkout.session.completed, invoice.paid, customer.subscription.updated and customer.subscription.deleted. Change nothing else."

Prompting can fix the code, not your Stripe account. Lovable's documentation says that with your own Stripe account you manage webhook endpoints and signing secrets yourself in the Stripe Dashboard. With built-in payments the rule runs the other way: do not create or delete webhooks by hand, and contact Lovable support if the Payments tab says the API key is invalid.

How do you fix the customers already affected?

Treat Stripe as the record of who has paid, and bring your database into line.

  • Compare every active, trialing and past-due subscription on Stripe's Subscriptions page with the plans in your app. Each mismatch is a customer to fix.
  • After the endpoint works, resend the failed events. Stripe lets you click Resend on an event in the Dashboard for up to 15 days after it was created, or use the Stripe CLI for up to 30 days. A manual resend does not stop Stripe's automatic retries, so your handler must skip repeats.
  • For older gaps, work from the subscriptions themselves. Stripe's list of events only returns the last 30 days.
  • Where emails differ, confirm the customer's identity, then make the two match in Stripe or in your app.
  • For double charges, cancel the extra subscription on Stripe's Subscriptions page. The cancel dialog lets you end it immediately and refund the last payment in full.

Stripe retries a failed delivery for up to three days in live mode, and three times over a few hours in a sandbox. If your endpoint was broken for longer, retries will not catch up. Production apps run a scheduled job that asks Stripe what changed and corrects any drift.

What is the durable fix?

  • Keep one record of who has paid, written only by your server from Stripe's events. The browser never decides.
  • Verify every signature with the live secret and the raw body, and keep server keys on the server. Our guide to the Supabase anon key and service role key explains which goes where.
  • Handle the full lifecycle. After invoice.paid, Stripe advises retrieving the subscription and confirming it is active before extending access. When it becomes past_due, ask for new card details. When it becomes canceled or unpaid, revoke access.
  • Log the event IDs you have handled and skip repeats. Stripe also says events may arrive out of order.
  • Turn on Stripe's setting that sends existing subscribers to the customer portal, so nobody buys the same plan twice.
  • Before every release, test purchase, renewal, failed payment, cancellation and refund in test mode. See our guide on testing an AI-built app.
  • Watch for failures: alert on failed deliveries and run the reconciliation job every night.

When should you stop prompting and get help?

Prompting suits a single bug pinned to a status code and a log line. Get an engineer when:

  • Customers have been double charged, or more than a handful are on the wrong plan.
  • The same payment bug has come back after three fixes.
  • The logs do not tell you which cause it is.
  • A Stripe or Supabase secret key may have reached browser code. Our guide to an exposed API key covers what to do first.
  • You are about to switch from test to live, or from one payment provider to another.

Our vibe-coding rescue service handles exactly this: find the cause, fix affected customers, add the safeguards. For a broader view first, the free production-readiness check scores your app and names its biggest risks.

A Plutonapps subscription then keeps payments and access in step as the app changes, with code review on every change. Plans are on our pricing page.

Sources

All checked on October 10, 2026.

Common questions

Why does Stripe show a payment but my Lovable app says I'm on the free plan?

The payment worked, but your app never recorded it. The usual causes are an upgrade that only runs on the success page, a missing or rejected webhook, test settings left after going live, or a database rule blocking the update. Stripe's Event deliveries tab for your webhook usually shows which one.

Does Lovable's Stripe integration use webhooks?

It depends on the setup. Built-in payments register webhook endpoints on your provider account automatically. When you connect your own Stripe account, Lovable's documentation says the app checks Stripe directly by default and uses webhooks only if you ask for them.

How long does Stripe retry a failed webhook?

In live mode, Stripe retries for up to three days with increasing gaps between attempts. In a sandbox it retries three times over a few hours. After that, you resend events by hand or reconcile from Stripe's records.

Can I resend a Stripe webhook after fixing my endpoint?

Yes. In the Stripe Dashboard you can click Resend on an event for up to 15 days after it was created, and the Stripe CLI can resend for up to 30 days. Make sure your handler skips events it has already processed, because automatic retries keep running too.

Why was my customer charged twice?

Usually because they ended up with two subscriptions, often after the app kept showing them as free. Cancel the extra subscription in Stripe and refund it, then turn on Stripe's setting that sends existing subscribers to the customer portal instead of a new checkout.

More on this: Lovable mastery & prompting · Lovable app not working in production? How to make it production-ready

Built something in Lovable you want people to rely on?

We are the engineers who take it the rest of the way — secured, tested, released and supported.