Subscription Downgrade Bugs: Keeping Premium After You Pay for Basic
Billing says Basic. Your JWT still says Pro. That gap is a privilege bug with a subscription wrapper.
Hunters under-test downgrade paths because upgrades get the attention. The dangerous moment is the transition down.
Demote, then poke every gated door
Subscribe to a paid plan on a test account. Exercise a clearly premium API—export, SSO config, advanced API keys, higher rate limits. Downgrade through the customer portal. Immediately retry the same premium calls with the existing session.
Outcomes that matter:
- Premium endpoints still return 200
- Feature flags in
/entitlementsstill list pro modules - Seats or projects above the new plan remain editable
- Old API keys keep elevated scopes
Refresh tokens and re-login once. If the bug only lasts until cookie expiry, severity drops but it remains valid if the window is long. If a new login still shows pro, entitlements never reconciled with the subscription.
Cancel instead of downgrade. Some products flip status: canceled yet leave plan: enterprise until period end—and then forget the end job. Schedule a check at period boundary if the program allows lingering access tests.
Client cache versus server truth
SPAs cache "pro" in local storage and keep showing admin nav. That alone is weak. Hit the server. I've filed strong reports where the UI looked basic but GET /admin/audit still worked because authorization checked a stale organizations.plan column the webhook never updated.
Webhooks from Stripe/Chargebee are a frequent root cause: customer.subscription.updated mishandled, or only deleted events processed. Mention if you can see delayed or missing entitlement updates in account timestamps without claiming access to their PSP dashboard.
Race upgrade and downgrade clicks. Parallel portal requests can leave the higher entitlement row behind.
Team plans add a twist: the owner downgrades while a member session keeps an old elevated role claim. Test with two accounts in one tenant—billing owner demotes, member retries admin APIs.
Report the privilege, not the invoice
Attach plan history, entitlement JSON before/after, and a privileged response after demotion. Quantify by list price of retained features. Suggest tying authorization to a billing-sourced entitlement service evaluated per request, invalidate sessions on plan change, and reconcile webhooks with idempotent handlers.
Scheduled downgrades add another wrinkle. Queue a downgrade for period end, then create premium resources on the last day. After the job runs, do those resources stay writable? Orphaned premium objects with working edit APIs are still a keep-privilege finding.
Export jobs started before downgrade and downloaded after are worth checking. If the async worker never rechecks plan state, you kept a premium capability past billing intent.
Add-ons cancelled independently of the base plan sometimes leave parent entitlements active. Cancel the add-on only, then call the add-on API. Or cancel the base plan while an add-on row still says active. Either mismatch is reportable when access continues.
Ask yourself one question before filing: would a support agent who refunded the customer still see this access as wrong? If yes, you are done writing the impact section.