Stripe owns two things that should be ours: the clock and the ledger. The clock decides when billing events happen — when subscriptions renew, when trials convert, when dunning escalates. The ledger decides what gets charged — line items, proration, amounts. Every billing decision runs through Stripe's infrastructure on Stripe's schedule. We react to webhooks. We don't drive the process.

Reactor is our billing engine, built on Cloudflare Workers and Durable Objects. It has the machinery to own both: state machines for subscription lifecycle, alarms for renewal scheduling, fragments for invoice generation, dunning chains for payment failure escalation. But machinery isn't proof. We need to validate that Reactor fires at the right time and charges the right amount — on real traffic, against Stripe's actual output.

Reclaim the Clock

Reactor independently schedules and fires renewals, trial conversions, and dunning — without being told.

Cut the Correct Invoice

When a billing event fires, Reactor generates an invoice with the correct dollar amount.

These outcomes are coupled. An invoice is a function of time: period boundaries determine proration, renewal timing determines when the next charge lands, dunning timing determines escalation consequences. The clock decides when. The invoice proves how much. Shadow billing validates both against Stripe's actual behavior — zero risk, because Stripe remains the system of record throughout.

Not all billing events are equal. API-driven events — subscription creates, cancels, plan changes — are synchronous. A customer calls our API, we call Stripe, we mirror to Reactor, we compare the response. Straightforward. Clock-driven events are the real test.

API-DrivenClock-Driven
ExamplesCreate, cancel, plan changeRenewal, trial conversion, dunning escalation
TriggerCustomer or system calls an APITime passes — an alarm fires
CoordinationMirror the call, compare the responseTwo systems independently arrive at the same result
DifficultyStraightforward — input drives outputHard — no shared trigger, timing must align

Clock-driven validation is what actually proves Reactor can replace Stripe. If Reactor's renewal alarm fires at the right time and produces the right invoice, it can drive billing independently. If it can only mirror API calls, it's a translator — not an engine.

Shadow billing is the validation methodology. Stripe stays the system of record. Every subscription lifecycle event is also processed by Reactor through its full billing pipeline. Reactor generates real invoices — not synthetic price-times-quantity computations. We compare Stripe's actual invoice subtotal against Reactor's generated invoice subtotal. Subtotal is the raw sum of line items before tax and credits: the purest signal of financial correctness.

API-Driven Path create cancel plan-change SYNCHRONOUS
Clock-Driven Path renewal trial-convert dunning ASYNCHRONOUS
Reactor Engine subscription-do invoice-do payment-do alarms FULL PIPELINE
Compare subtotal status timing VS STRIPE
mirror goroutine independent alarms real invoice

Two paths into the engine. API-driven events are mirrored via a background goroutine — zero latency impact on the Stripe critical path. Clock-driven events fire from Reactor's own alarms — independent of Stripe. Both paths produce real invoices that get compared against Stripe's actual output.

Validation matrix
EventTypeWhat We CompareWhat It Proves
Create (active)APIInvoice subtotalReactor computes the right charge
Create (incomplete)APIInvoice subtotalSetup intent flow produces correct invoice
Create (trialing)APIStatus onlyTrial starts without charging
CancelAPISubscription statusLifecycle termination works
RenewalClockInvoice subtotal + timingReactor's clock fires correctly — the core proof
Trial conversionClockInvoice subtotal + timingReactor converts and charges at trial_end
Dunning escalationClockState transition + timingReactor escalates on the right cadence
Plan changeAPIProrated invoice subtotalDeferred — proration model mismatch
StatusDRAFT
DriverJared Goguen
Repossubscriptions-api (Go), reactor (TypeScript / Cloudflare Workers)
WhatStatusDetails
Reactor client (MR !9608)In reviewHTTP client and billingprovider integration. Foundation for all shadow work.
Shadow comparison (MR !9630)DraftComparison logic and tests. Needs rewrite for Mirror() architecture.
Reactor engine75+ commitsSubscription lifecycle, invoice generation, payment stubs. Personal fork.
Clock validationNot startedRenewal, trial conversion, and dunning comparison.
MilestoneGateProves
Create comparisonSubtotals match for active, incomplete, trialing on stagingInvoice correctness (API)
Renewal comparisonReactor alarm fires at period_end, invoice subtotal matchesClock + invoice
Trial conversionReactor converts at trial_end, first paid invoice matchesClock + invoice
Dunning comparisonReactor escalation matches Stripe's state transitionsClock + state
Confidence thresholdN consecutive days of 100% match across all event typesCutover readiness
#QuestionImplications
1How do we correlate Stripe's renewal webhook with Reactor's independent renewal alarm?Core mechanism for clock validation. Can't compare async events without correlation.
2What timing tolerance is acceptable for clock-driven events?Exact-second match is unrealistic. Need to define 'close enough.'
3Should Reactor's clock run freely or be seeded from Stripe's period boundaries?Free-running is the real proof but harder. Seeded is safer but less conclusive.
4How does dunning comparison work when Stripe's retry schedule is configurable?Reactor must match Stripe's configured schedule, not an independent one.
RiskImpactMitigation
Clock driftCriticalReactor and Stripe alarms may not fire at exactly the same moment. Needs timing tolerance window.
Event correlationCriticalMatching async events across two systems requires a correlation mechanism — no shared trigger to join on.
Proration model mismatchHighStripe creates proration line items; Reactor voids and replaces. Plan change comparison deferred until models align.
Base MR stalenessMediumMR !9608 has merge conflicts with staging. Must rebase before shadow work proceeds.