Skip to main content
← Back to list
01Issue
FeatureOpenSwamp ClubPublic
AssigneesNone

Relationships

#2232 Billing telemetry: surface scheduled cancellations (cancelAtPeriodEnd) and emit subscription_canceled reliably

Opened by keeb · 9/17/2026

Problem

A customer who cancels in Stripe (billing portal or dashboard) is invisible in telemetry until their period actually ends.

  • Cancelling sets cancel_at_period_end=true and Stripe sends customer.subscription.updated. The gateway maps it to subscription.synced (lib/infrastructure/stripe/stripe-billing-gateway.ts:430), and routes/api/billing/webhook.ts:35-51 emits subscription_synced with status: "active". The properties (collectiveId, plan, interval, seats, status, username) omit cancelAtPeriodEnd, so the cancel is indistinguishable from a routine renewal sync. The flag is persisted to Mongo (lib/app/billing/sync-subscription.ts:250) but never reaches the event lake.
  • subscription_canceled only fires on customer.subscription.deleted at period end (webhook.ts:66-77). Churn shows up the day it happens, never while it is still pending.
  • subscription_canceled is sent with fire-and-forget track(...).catch(() => {}), while subscription_started uses reliableTrack. A dropped emit loses the churn event with no retry; only Mongo billing_subscription.status records it.

Observed: a paying Team customer opened the billing portal and cancelled on 2026-08-21. The only trace in ClickHouse is a billing_portal_opened followed 25s later by a subscription_synced with status: active. Nothing in the lake says the subscription won't renew.

Proposed solution

  1. Add cancelAtPeriodEnd (and currentPeriodEnd) to the subscription_synced properties in webhook.ts.
  2. Emit a distinct subscription_cancel_scheduled event when a synced snapshot flips cancelAtPeriodEnd false→true, and subscription_cancel_reverted on true→false. Compare against the stored doc before applyStripeState.
  3. Send subscription_canceled with reliableTrack, same as subscription_started. Churn is as revenue-critical as a new customer.

Alternatives

  • Read billing_subscription.cancelAtPeriodEnd from Mongo directly. This works for one-off checks but keeps churn out of the lake, so the ClickHouse/Grafana revenue boards can't show pending churn.
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

9/17/2026, 12:04:08 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.