Diagnostic guide
customer paid but has no access
One customer emailed. The uncomfortable part is not their account. It is that whatever broke theirs has been running for a while, and the only reason you know about this one is that they bothered to write. Fix them first, then find the rest, because the second group is always larger than the first.
most likely causes, in order
01The webhook never landed the write+
how it looks
The payment is in Stripe. The row says free. Delivery may even show 200.
how to check
Stripe Dashboard → Developers → Events. Find their checkout.session.completed and look at the delivery attempts.
the fix
Correct the underlying handler, then replay the event rather than editing the row by hand. A manual edit fixes one person and hides the pattern.
02They paid through a Payment Link or the dashboard+
how it looks
There is no app row at all, not even a broken one. They may have no account.
how to check
Open the customer in Stripe and look at how the subscription was created.
the fix
Onboard them manually: this one is legitimate. Worth deciding whether Payment Links stay part of how you sell.
03Two accounts, one payment+
how it looks
They paid with one email and signed in with another. Both records are individually correct.
how to check
Search your users table for both addresses.
the fix
Merge, or attach the Stripe customer id to the account they actually use.
04The write succeeded but the app reads something else+
how it looks
The row looks right. The dashboard still says free.
how to check
Find the exact condition your access check evaluates. A subscribed boolean and a subscription_tier string can disagree.
the fix
Make one column authoritative and have everything read that.
check it yourself
Everyone currently in this state. Run this in your SQL editor, adjusting the table and column names to match your schema.
select email, stripe_customer_id, subscribed, subscription_tier, subscription_end, created_at from public.subscribers where stripe_customer_id is not null and (subscribed = false or subscription_tier is null) order by created_at desc;
what this does not tell you
That query finds people whose payment did not reach your database. It cannot find people whose cancellation did not reach it either, and those never write in, because free access is not a complaint anyone files.
Read-only, both sides. Three findings free, no account.
related
Stripe webhook not updating database
Your Stripe webhook returns 200 but the row never changes. The five causes, how to tell them apart, and the SQL to find everyone already affected.
Lovable Stripe subscription not working
Checkout succeeds but the app still shows the free plan. The specific things that break in Lovable and Supabase builds, and how to verify each one.