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