Skip to content

Stripe webhooks and reconciliation ​

Stripe is an asynchronous source of truth. Checkout redirects are user experience signals, not proof that money was collected or a subscription is active.

Receive, persist, then acknowledge ​

Both the MicroAuth platform Stripe endpoint and each tenant Stripe endpoint follow the same sequence:

  1. Read the unmodified request body and verify Stripe-Signature with the signing secret for that exact endpoint.
  2. Validate that the event belongs to the expected Stripe account and tenant context.
  3. Insert the event into a durable inbox with a unique Stripe account and event ID. Store processing status, attempt count, next-attempt time and the latest error.
  4. Return a successful response only after the insert commits. A database failure returns a retryable error to Stripe.
  5. Let the worker claim pending rows, process them idempotently and mark them complete. A transient failure records diagnostics and schedules backoff.

A replay of an already stored event is safe and receives a successful response. In-memory or Redis-only deduplication is not sufficient because a restart or eviction must not make an old financial event new again.

Fulfilment rules ​

  • Credit a one-time top-up only after Stripe confirms the payment is paid. Support asynchronous payment success; do not fulfil from the browser redirect alone.
  • Grant subscription credits only for eligible subscription creation or normal cycle invoices. Do not grant a full period's credits for arbitrary proration, draft or unrelated invoices.
  • Validate currency, amount, Stripe customer, tenant, portal workspace, subscription, Price and connected account before changing local state.
  • Use stable ledger references for top-ups, invoice grants, refunds and disputes. Replaying the same financial event produces no second adjustment.
  • Apply refunds and disputes as idempotent reversals when the corresponding payment is known. Flag unmatched events for reconciliation instead of guessing.
  • Never clear a local subscription ID merely because an outbound Stripe cancellation call failed. Clear or replace it only from confirmed Stripe state.

Failed payments and grace ​

invoice.payment_failed starts a 7-day grace period for the affected MicroAuth tenant or portal customer. During grace:

  • existing paid entitlements remain available;
  • the UI shows past_due, the grace deadline and a payment-recovery action;
  • additional failures do not move the original deadline forward.

A successful payment clears past_due, grace and billing suspension. If the deadline expires first, the worker sets billing_suspended while preserving the account, configuration and customer data. Recovery restores service after Stripe confirms payment. Only the billing-induced suspension marker is cleared; a separate manual suspension remains in force.

Payment recovery and cancellation ​

Portal team admins and billing managers use Billing settings to open a Stripe Billing Portal session. That is the current recovery path for updating a payment method, paying a recoverable invoice, or managing a customer subscription. The browser return alone does not restore service. A paid invoice or reconciliation result must confirm recovery first.

POST /api/v1/billing/subscription/cancel does not cancel remotely yet. It returns 409 for a live subscription and tells the caller to open Billing settings or contact support. This is deliberate: the API never clears local subscription IDs while Stripe might still bill the customer.

Voluntary customer cancellation is separate from payment failure. When Stripe keeps a subscription active until the end of its period, MicroAuth keeps its current plan while that subscription remains active. After Stripe reports the subscription deleted, the webhook handler clears the linked plan and subscription ID, marks the state canceled, and removes billing-grace markers. Feature downgrade checks can block other tenant configuration changes while live customer subscriptions or open subscription checkouts still exist.

Reconciliation ​

Run reconciliation on a schedule and after an incident. It should:

  1. reclaim inbox rows stuck in processing past their lease;
  2. retry due failed rows with bounded exponential backoff;
  3. compare local subscription, invoice and payment state with Stripe;
  4. import missed Stripe events or repair local state through the same idempotent handlers;
  5. expire grace periods and restore accounts whose payments recovered;
  6. report unmatched account, Price, customer and metadata ownership instead of applying it.

Alert on the oldest pending event, repeated failures, reconciliation drift, grace expirations, unmatched reversals and worker queue age. Keep enough event metadata for diagnosis while applying the privacy retention schedule to raw payloads.

Incident checklist ​

  1. Keep the webhook endpoint available and preserve failed inbox rows.
  2. Confirm database and worker readiness before manually replaying anything.
  3. Fix the handler or ownership data.
  4. Retry through the inbox or reconciliation command, not by directly editing balances or subscription rows.
  5. Verify the ledger reference, inbox status and Stripe object agree.
  6. Record the incident and any manual action in the audit trail.

MicroAuth is a product of Zyref, LLC.