Diagnostic guide

stripe dispute or chargeback, still has access

A dispute is two separate problems wearing one event. One is the money, which you answer in Stripe before the deadline. The other is access, which nothing changes unless your webhook explicitly does it, and most generated webhooks never subscribed to the event at all.

most likely causes, in order

01charge.dispute.created is not handled+

how it looks

Every past dispute, win or lose, has left access exactly as it was. There is no code path that runs when one opens.

how to check

Search the webhook handler for charge.dispute.created. The event carries a charge id, not a customer id directly: the handler has to retrieve the charge to find its customer.

the fix

Add the event. Retrieve the charge, find the row by its customer, and apply whatever your policy is (below).

02No policy was decided, so access was intentionally left alone+

how it looks

This one is not necessarily a bug: someone decided, or defaulted into, keeping access on during an open dispute.

how to check

This is a decision, not a check: most apps end access while a dispute is open, since the money is already pulled back and a large share of disputes are fraud. Some keep long-standing customers on and restore access automatically if the dispute is won.

the fix

Pick the rule. If disputes should end access, handle the event. Either way, answer the dispute itself in Stripe before the deadline (usually 7 to 21 days, depending on the card network); ending access does not respond to it, and an unanswered dispute is lost automatically.

03Access is revoked but never restored after a won dispute+

how it looks

A customer who won their dispute, which does happen, stays locked out permanently even though the money came back.

how to check

Look for charge.dispute.closed in the handler. If only .created is handled, there is no path back to access.

the fix

Handle charge.dispute.closed and restore access when the dispute's status is won and the subscription is still active.

04The dispute event fires, but the customer lookup fails silently+

how it looks

The handler runs without error. The row is untouched because the lookup never found a match.

how to check

The dispute's charge has a customer attached only if the charge itself was created with one: a charge from an older Payment Link or a one-off checkout can lack it.

the fix

Log a failed lookup instead of dropping it silently, and confirm checkout always attaches a customer to the charge.

what this does not tell you

None of this shows up from your own table: a dispute does not change any column here by default. The only way to find who is currently disputed and still has access is to compare against Stripe directly.

Read-only, both sides. Three findings free, no account.

related