Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

When a customer who has canceled can still open a paid feature, the cause is almost always in the application, not in Stripe. Stripe records the subscription’s status accurately. Your application has to read that status, or the subscription events that announce it, and change its own access rules. Stripe does not publish how often this happens, and its documentation does not describe a platform defect behind it. So treat this as a checklist for your handler and your access check, not as a known Stripe bug.

Stripe stores the subscription state; your app grants the access

Stripe’s guide to using webhooks with subscriptions lists revoking a customer’s access after cancellation as logic the integration itself must perform. Stripe’s Entitlements feature works the same way: it emits an event that tells your system to provision or de-provision features, and your code has to act on it. Stripe will not remove a feature from your product on your behalf.

That split explains most of the bug reports. The subscription object in Stripe can be correct while the access record in your database, session, or cached token is still granting the old permission.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A cancellation request is not the same as a canceled subscription

A cancellation is an action taken on a subscription. The subscription reaches a terminal state only after that action takes effect. Access should follow the state, not the button press. Stripe supports two timings, and they behave differently:

  • Immediate cancellation. Stripe’s Cancel a subscription API reference says the subscription is returned with status canceled. Stripe also states that the customer will not be charged again for that subscription. Pending invoice items can still be charged in some cases, and Stripe stops automatic collection of finalized invoices by default when a subscription is canceled. Those billing consequences are separate from your access policy.
  • Cancellation at the end of the billing period. The subscription keeps its current status until the period ends. Its cancel_at_period_end field indicates that the cancellation is scheduled. If your product promises access until the paid period ends, your access check must allow that window and then revoke access when the status changes.

Before writing any access rule, decide which timing your product sells and document the end date the customer is promised.

What each subscription status should do in your access check

Stripe describes each status and gives a recommendation for some of them. The table below shows how to map them to access. The Stripe wording comes from the subscription webhooks guide and the Subscriptions overview.

Status What Stripe documents Suggested access policy
trialing Stripe says it is safe to provision the product during the trial. Grant access for the trial, under your trial policy.
active Generally in good standing. Stripe cautions that active does not necessarily mean every outstanding invoice is paid, depending on status-resolution settings. Grant access. Do not treat it as proof that the latest invoice was paid.
past_due A payment on a finalized invoice failed or was not attempted. Stripe may retry, but the status does not guarantee another attempt. Grant access only through a grace period you define and state publicly. Do not silently treat every payment failure as cancellation.
unpaid Set after retry handling, based on your Dashboard settings. Stripe recommends revoking access. Revoke the relevant features.
canceled Terminal state. The subscription cannot be updated again. Stripe says to revoke access when this state is reached. Revoke the relevant features.
paused Distinct from pausing payment collection. Stripe documents separate events and behavior for each. Set an explicit policy. Do not map it to canceled by default.

Subscribe to the events you need, and no more

Confirm that your webhook endpoint listens for the event types the integration actually uses. Stripe’s event reference covers these areas, and the ones most relevant to access include:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • customer.subscription.created
  • customer.subscription.updated, for changes to a subscription that is still running
  • customer.subscription.deleted, for a subscription that has ended
  • customer.subscription.paused and customer.subscription.resumed, if your product uses pausing
  • customer.subscription.trial_will_end, if you send trial reminders or change access at trial end
  • invoice.paid and invoice.payment_failed, for payment-driven access decisions
  • entitlements.active_entitlement_summary.updated, if you use Stripe Entitlements

Listening to every event type adds load and noise to your handler without improving access control.

Build the handler so it can be retried safely

Stripe may deliver the same event more than once, and it retries failed deliveries. The following sequence is the one Stripe’s Webhooks documentation points toward.

  1. Verify the signature against the raw body. Use the unmodified request body, the Stripe-Signature header, and the signing secret for that endpoint. Reject requests whose signature does not verify. If a framework parses JSON before your code runs, the signature check will fail or pass for the wrong reason, so capture the raw bytes first.
  2. Check whether the event ID was already processed. If it was, return a successful 2xx response and do nothing else.
  3. Record the event ID and return a 2xx response quickly. Stripe recommends doing the heavy work after acknowledging the event.
  4. Queue the work when processing may be slow. Stripe’s webhook best practices say: “Configure your handler to process incoming events with an asynchronous queue.”
  5. Retrieve the current subscription from the API and apply the access policy. Make the change idempotent, so that running it twice leaves the same access state.

Why ordering and timestamps cannot be trusted

Stripe’s webhook documentation states: “Stripe doesn’t guarantee the delivery of events in the order that they’re generated.” Distinct events can also share a timestamp, so the event created value is not a safe way to decide which event is newer or whether an earlier event has been processed.

Consider this sequence. A subscription is canceled, so customer.subscription.deleted is generated. A later customer.subscription.updated event for the same subscription is delivered first, and your handler grants access because the payload shows an active status. The deleted event then arrives and revokes access. That final state may be right, but only by luck. If the order were reversed, access would be re-granted after cancellation. The fix is to ignore the event payload as the source of truth and fetch the subscription’s current status before changing access.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Duplicate events about the same underlying object should be recognized by combining the object ID with the event type. Stripe recommends this when several Event objects refer to the same object.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose between Stripe Entitlements and your own access state

Stripe documents two access-control approaches. Neither removes the need for secure webhook handling and reconciliation.

Approach How it works Trade-offs
Stripe Entitlements You associate subscription products with features. Your system responds to entitlements.active_entitlement_summary.updated to provision or de-provision features. Fits when your access model is feature-based. You must map Stripe features into your own authorization model, and the implementation effort depends on how many features you have.
Application-maintained access state You track subscription status or a local access expiration timestamp, and reconcile it against Stripe. Gives you control over grace periods and local policy. Reconciliation is more complex, and stale state appears if event handling or customer-to-account mapping fails.

If you keep a local expiration timestamp, extend it only after a payment is confirmed. Stripe’s guide says to retrieve the associated subscription after invoice.paid and confirm that its status is active before extending access. A paid invoice alone does not guarantee that the subscription is active. Also check the expiration timestamp on each login or session start, so that a missed cancellation event does not keep access open indefinitely.

Diagnose an affected customer

When a specific customer still has access, work through these steps in order:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. In the Stripe Dashboard, open the customer’s subscription and note its current status and, if applicable, whether a cancellation is scheduled for the period end.
  2. Open the event delivery history for your webhook endpoint and find the customer.subscription.deleted or customer.subscription.updated event for that subscription. Check whether delivery succeeded, failed, or is still being retried.
  3. If the event failed, resend it from the Dashboard or with the Stripe CLI, within the windows in the table below. A manual resend does not cancel Stripe’s automatic retries, so the same event may be delivered more than once. Your duplicate check should handle that.
  4. Search your handler logs for that event ID. If the event was received and acknowledged but access was not changed, the problem is in your processing logic.
  5. Compare the access record in your database with the subscription’s current status. Revoke or correct access according to your policy, and confirm that the change persists after a fresh login.

Delivery windows to check during an incident

Delivery behavior Window documented by Stripe
Automatic retries for live-mode events Up to three days, with exponential backoff.
Automatic retries for sandbox events Three retries over a few hours.
Dashboard resend Up to 15 days after the event was created.
Stripe CLI resend Up to 30 days after the event was created.

These are the windows in Stripe’s Webhooks documentation, which Stripe may revise, so check the current page before relying on them. They describe how long a delivery can be retried. They are not evidence that a particular event was lost.

Common causes, checked in this order

  • No handler for customer.subscription.deleted. The subscription is canceled in Stripe, but no code revokes access for that event.
  • Access granted on invoice.paid without a status check. A paid invoice does not confirm that the subscription is active.
  • Ordering by timestamp. An older event processed after a newer one can re-grant access.
  • Handler returns an error or times out. Stripe will retry, but the work may never finish if the handler fails on every attempt.
  • Signature verified against a parsed body. Verification fails, or your team disables it to get events working, which leaves the endpoint open to forged requests.
  • Customer-to-account mapping is wrong. Revocation runs against the wrong local account, or against none.
  • Past-due treated as active forever. No grace period is defined, so access continues through repeated payment failures.
  • Cached access in tokens or sessions. The database is correct, but a long-lived token still grants the feature until it expires.
  • Sandbox and live endpoints confused. A test endpoint receives the events, while the production endpoint never does.

Before release, test the integration in a sandbox or with the Stripe CLI, and confirm that a canceled test subscription removes access in your application. Stripe explicitly recommends this step.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.