Skip to main content
Local plan state is a cache of what the billing provider says. Every billing incident is therefore the same shape: the cache and the provider disagree, and a customer is on the wrong side of it. Two failures matter and they are not equally urgent. A customer wrongly locked out is losing something they paid for. A customer wrongly served is costing you capacity. Fix the first one first.

Symptoms

  • billing.webhooks.failing pages, from a stored billing event that recorded a failure.
  • billing.reconcile.discrepancy arrives when events pile up unprocessed.
  • asobeast_billing_events_unprocessed climbs and does not drain.
  • A customer reports a 402 after paying, or keeps full access after cancelling.

Triage

1

Check the webhook secret before anything else

An unverified endpoint refuses every delivery, so a paid subscription never provisions. If STRIPE_WEBHOOK_SECRET is empty or stale, nothing else you do will hold.
2

Read the workspace

storedPlan is what the database holds. plan is what entitlement resolves right now, which is what the customer actually experiences.
3

Compare with the provider

Open the same customer in the provider dashboard. The provider is the source of truth for the subscription; the database is not.

Containment

Never edit a workspace plan directly in the database. It drifts back at the next reconciliation and you lose the record of why it changed.
  1. Reconcile the one workspace, which corrects local state from the provider and records who did it and why:
  2. Reconciliation looks for a subscription on the workspace customer as well as by the stored id, so a customer who paid and whose webhook never landed is repaired by this step rather than needing the id pasted in by hand.
  3. If many workspaces are wrong, fix the webhook path first. Reconciling everyone against a broken cause just delays the same incident.
  4. Replayed events are safe. Webhook handling is idempotent and a stored event is processed once.

Recovery

  1. Confirm the daily reconciliation from CRON_BILLING_RECONCILE runs clean, with asobeast_billing_events_unprocessed back at zero.
  2. Confirm asobeast_billing_events_failed is zero. It pages precisely so that a single failure does not sit unnoticed.
  3. Re-register the webhook endpoint with the provider if the secret changed, and verify with one test delivery.

Who to tell

  • The affected customer, directly, the same day. Say what happened, that access is restored, and whether they were charged.
  • Every customer, only when checkout itself was down. A lockout affecting a handful of accounts is handled account by account.
  • Nobody, for a payment that failed for the customer’s own reason. Dunning already emails them a portal link.
Last modified on September 3, 2026