Customer paid but has no access? Stripe in AI-built apps
Why a customer pays through Stripe and your Lovable or Bolt app doesn't grant access: webhooks, test and live keys, email matching and subscription events.
Leer en español →
When a customer pays and your app still treats them as a free user, Stripe almost always has the money and your app never recorded it. The usual causes are a webhook that isn't arriving or is failing, test and live keys mixed up, or a payment that can't be matched to the right user. You can find which one it is by checking the payment in Stripe, then the webhook's delivery log, then your function logs, in that order.
This guide walks through those checks for apps built with Lovable, Bolt, v0, Replit or Cursor, most of which use Stripe Checkout with Supabase Edge Functions behind it.
How does your app find out that someone paid?
There are three ways an app can learn about a payment, and AI-built apps use all of them.
- The success page. After paying, Stripe sends the customer back to a page in your app, and the app grants access there. This is the weakest option. If the customer closes the tab, loses connection or the redirect fails, your app never hears about it.
- Webhooks. A webhook is a message Stripe sends to an address on your server when something happens, such as a completed checkout or a renewed subscription. This is what Stripe recommends for granting access, because it arrives even if the customer never returns to your site.
- Checking Stripe directly. Some integrations ask Stripe for the customer's status each time they sign in. Lovable's own Stripe integration works this way by default, matching the user to the Stripe customer by email address.
Knowing which one your app uses tells you where to look. If you're not sure, ask the AI to explain how the app grants access after payment, without changing anything.
Step 1: Find the payment in Stripe
Open the Stripe Dashboard and look up the payment by the customer's email.
- Check the mode. Stripe keeps test data (in sandboxes or test mode) completely separate from live data. If a real customer paid real money, the payment is in live mode. If your app is still using test keys, it will never see it.
- Check the status. "Succeeded" means Stripe has the money. Anything else (incomplete, failed, requires action) means the problem is in the checkout itself.
- Check the email. Write down the exact email on the Stripe customer record. You'll need it in step 4.
Step 2: Read the webhook's delivery log
If your app uses webhooks, open Workbench → Webhooks in Stripe and select your endpoint. The Event deliveries tab shows every event Stripe tried to send and what your server answered.
First, confirm there is an endpoint in live mode. A common mistake is creating the webhook while testing and never creating the live one.
Then look at the status codes:
| What you see | What it usually means |
|---|---|
| 401 or 403 | Your function rejected the request before reading it. Supabase Edge Functions expect a signed-in user's token by default, so a webhook function needs that check turned off and must rely on Stripe's signature instead. |
| 400 | Signature verification failed. Usually the wrong signing secret (whsec_…), the test secret used in live mode, or code that parsed the body before verifying it. Stripe needs the raw, untouched body. |
| 404 | The URL is wrong, often because the function was renamed or redeployed under a different name. |
| 3xx | Your server redirected the request (for example from the root domain to www). Stripe counts redirects as failures. Use the final URL. |
| 500 or timeout | The code ran and crashed, or took too long. Read the function logs. |
Stripe signs every webhook with a secret that is unique to each endpoint, and the secret for test mode is different from the one for live mode. If you copied it while testing, it won't work in live.
Step 3: Read your function logs
In Supabase, open Edge Functions, pick the function that handles Stripe (often named something like stripe-webhook) and open its logs. Filter to the time of the payment. Look for:
- Errors about the signature or a missing secret.
- Errors writing to the database, such as
new row violates row-level security policy. Your webhook function may be trying to update a table it isn't allowed to touch. - A user that couldn't be found.
If you use Lovable Cloud, the logs are in Lovable's Cloud view. On Bolt, Vercel or Replit, look in that platform's function or deployment logs.
Step 4: Check that the payment matches the right user
A webhook can arrive perfectly and still grant nothing, because the app can't tell which user paid.
- Email mismatch. If your app matches by email, a customer who signs in with
ana@company.comand pays withana.personal@gmail.comwon't match. This is one of the most common causes, and it's invisible until you compare the two addresses. - No link between the user and the checkout. A sturdier setup passes the signed-in user's ID when it creates the Checkout Session (Stripe has a
client_reference_idfield for this, andmetadatafor anything else) and saves the Stripe customer ID on the user's record. Then the webhook knows exactly who paid.
Open the user's row in your database and check whether it has a Stripe customer ID and a subscription status. If those fields are empty, the webhook never wrote them.
Why do test and live keys cause so many problems?
Moving from test to live means changing several things at once, and missing any one breaks payments.
- API keys. The secret key (
sk_live_…instead ofsk_test_…) in your server's secrets, and the publishable key in the front end if your app uses one. - Products and prices. Prices created in test mode don't exist in live mode, and their IDs are different. If your code has a test price ID written into it, live checkout fails.
- Webhook endpoint and signing secret. A separate live endpoint, with its own
whsec_secret stored in your server. - Redeploy. Some platforms only read new secrets after a new deployment.
One more warning from Lovable's documentation: its preview has no separate test environment, so if you connect a live key, payments made from the preview are real.
What do subscriptions need beyond the first payment?
A one-time purchase needs one event. A subscription needs your app to keep up with changes for as long as it lasts. The events most apps need to handle are:
checkout.session.completed: the customer finished checkout. Create or link the subscription.invoice.paid: a payment went through, including each renewal. Keep access open.invoice.payment_failed: a renewal failed. Warn the customer before cutting access.customer.subscription.updated: plan changes, or a cancellation scheduled for the end of the period.customer.subscription.deleted: the subscription ended. Remove access.
Two details trip up many apps. Stripe doesn't guarantee the order of events, so your code shouldn't assume checkout.session.completed arrives before invoice.paid. And the same event can arrive twice, so record each event ID you've processed and skip repeats.
How do you fix it for customers who already paid?
Once the cause is fixed, you still have customers waiting.
- Let Stripe retry. In live mode, Stripe keeps retrying failed deliveries for up to three days. If you fix the endpoint in time, many events will arrive on their own.
- Resend events. From the Dashboard you can resend a specific event for up to 15 days after it was created.
- Grant access by hand for anyone older, after confirming their payment in Stripe.
Then write to those customers. A short, honest message keeps most of them.
What should you never do while fixing this?
- Never paste a Stripe secret key into the AI chat, a code file or a message. Put it only in the secrets store your platform provides.
- Never turn off signature verification to make the 400 errors go away. That lets anyone send a fake "payment completed" message to your app.
- If a secret key was ever exposed, roll it in the Stripe Dashboard and update your server.
Our security checklist for AI-built apps covers keys in more detail.
Quick answers
The customer paid and the success page loaded. Why no access? The success page may not be what grants access. Check the webhook deliveries and the user's row in the database.
Can I test webhooks without real money? Yes. Use a sandbox or test mode with Stripe's test cards, and the Stripe CLI can send test events to your endpoint.
Should I ask the AI to fix it? Give it the evidence first: the status code from Event deliveries, the log error and which mode you're in. Ask it to explain the cause before changing anything.
Still not getting paid customers through?
Payment bugs cost you money every day they stay open. Run the free App Health Check, see how our AI app rescue works, or look at our pages for Lovable and Bolt apps. If you want a full review, the App Rescue Audit is $299 with a written report in 2 business days. Tell us what's happening and a person will reply within one business day.


