Get an engineer for this one. Needs webhook code for cancellation events.
What you see. A customer cancelled weeks ago and Stripe shows the subscription as canceled, but they can still use every paid feature.
Why it happens
Most AI-built apps set a paid flag to true after checkout and never touch it again. Stripe reports the rest of the subscription's life through webhook events, and your code has to act on them. When a customer cancels, Stripe sends customer.subscription.updated with cancel_at_period_end set to true, then customer.subscription.deleted when the period ends or when the cancellation is immediate. Failed renewals send invoice.payment_failed, and the status moves to past_due, then canceled or unpaid depending on your settings.
Stripe's guidance is to revoke access when the status becomes canceled or unpaid. If your webhook only listens for checkout.session.completed, none of this reaches your database. A second cause is storing a yes or no flag. Stripe recommends storing the subscription status and an access expiry date, then checking them when the customer logs in. Our checklist item PAY-04 covers exactly this: failed payments, cancellations, and refunds tested end to end.
Fix it yourself
- In Stripe, open the canceled subscription and note its status and customer ID. In your database, find that user and compare. If your table only has
is_paid, the model is too simple. - Open Developers → Webhooks and check the live endpoint subscribes to
customer.subscription.updated,customer.subscription.deleted, andinvoice.payment_failed. - Ask the agent to add columns for
stripe_customer_id,subscription_status, andcurrent_period_end, and to update them from each event after verifying the signature. - Ask it to gate paid features on a status of
activeortrialingplus a futurecurrent_period_end, checked on the server, not only hidden in the interface. - Test in a sandbox: subscribe, cancel at period end, then cancel immediately, and confirm access ends each time.
- Fix existing customers with a one-off script that reads each subscription from Stripe and writes the real status.
When to get an engineer
Stop here if the words webhook and signature are new to you, because wiring these events wrong can lock out paying customers. Tell the engineer how many subscribers you have, where the current paid flag lives, and whether the webhook runs in a Supabase edge function or your app server. A single fix at €99–$990 over 1–3 days usually covers the events, the columns, and a backfill. Ask for tests on the money flows, our item CODE-02.
Top-rated companies for Supabase apps
Ranked by our score: published prices, scope of work, AI tools named, timing and a low-risk first step. How we score.
- 1Redlab10.0/10Best for: A full rescue by one team that keeps buildingFree first step · from $490
- 2Vibe Code Rescue (Lev Arakelyan)9.0/10Best for: A one-off check and a cleanupFrom $299
- 3LazyCodeLab9.0/10Best for: A one-off check and a cleanupFrom $399
Check these
Failed payments, cancellations and refunds are tested
Use your provider's test cards for declines and disputes, then check what the user sees and what access they keep.
Paid accounts are unlocked by a verified payment webhook
Access should change only when your server receives a signed webhook from Stripe or your provider, not when the browser reaches a success page.
The flows that make money have tests
Sign-up, checkout and the core action. A few end-to-end tests catch most regressions from new prompts.
Admin actions are checked on the server
Hiding an admin button is not protection. Call the admin endpoint as a normal user and confirm it is refused.
Questions
Can I just check Stripe on every page load?
You can call Stripe from the server to confirm status, but it is slow and fails when Stripe is unreachable. Store the status from webhooks and check your own database, with Stripe as the source of truth.
Why does the customer keep access until the month ends?
That is correct behavior when they cancel at period end. Stripe sends customer.subscription.deleted when the paid period finishes. Access should end on that event, not at the moment they click cancel.
Sources: Stripe: Cancel subscriptions; Stripe: Using webhooks with subscriptions; Stripe: Receive events in your webhook endpoint.
