Q2 2026 ✦ Reactor
Shadow Billing Platform
Prove Reactor can replace Stripe by validating the two things that matter: the billing clock and the invoice. Run both systems on real traffic, compare the results, build confidence for cutover.
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.
Reactor independently schedules and fires renewals, trial conversions, and dunning — without being told.
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-Driven | Clock-Driven | |
|---|---|---|
| Examples | Create, cancel, plan change | Renewal, trial conversion, dunning escalation |
| Trigger | Customer or system calls an API | Time passes — an alarm fires |
| Coordination | Mirror the call, compare the response | Two systems independently arrive at the same result |
| Difficulty | Straightforward — input drives output | Hard — 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.
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.
| Event | Type | What We Compare | What It Proves |
|---|---|---|---|
| Create (active) | API | Invoice subtotal | Reactor computes the right charge |
| Create (incomplete) | API | Invoice subtotal | Setup intent flow produces correct invoice |
| Create (trialing) | API | Status only | Trial starts without charging |
| Cancel | API | Subscription status | Lifecycle termination works |
| Renewal | Clock | Invoice subtotal + timing | Reactor's clock fires correctly — the core proof |
| Trial conversion | Clock | Invoice subtotal + timing | Reactor converts and charges at trial_end |
| Dunning escalation | Clock | State transition + timing | Reactor escalates on the right cadence |
| Plan change | API | Prorated invoice subtotal | Deferred — proration model mismatch |
| Status | DRAFT |
|---|---|
| Driver | Jared Goguen |
| Repos | subscriptions-api (Go), reactor (TypeScript / Cloudflare Workers) |
| What | Status | Details |
|---|---|---|
| Reactor client (MR !9608) | In review | HTTP client and billingprovider integration. Foundation for all shadow work. |
| Shadow comparison (MR !9630) | Draft | Comparison logic and tests. Needs rewrite for Mirror() architecture. |
| Reactor engine | 75+ commits | Subscription lifecycle, invoice generation, payment stubs. Personal fork. |
| Clock validation | Not started | Renewal, trial conversion, and dunning comparison. |
| Milestone | Gate | Proves |
|---|---|---|
| Create comparison | Subtotals match for active, incomplete, trialing on staging | Invoice correctness (API) |
| Renewal comparison | Reactor alarm fires at period_end, invoice subtotal matches | Clock + invoice |
| Trial conversion | Reactor converts at trial_end, first paid invoice matches | Clock + invoice |
| Dunning comparison | Reactor escalation matches Stripe's state transitions | Clock + state |
| Confidence threshold | N consecutive days of 100% match across all event types | Cutover readiness |
| # | Question | Implications |
|---|---|---|
| 1 | How 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. |
| 2 | What timing tolerance is acceptable for clock-driven events? | Exact-second match is unrealistic. Need to define 'close enough.' |
| 3 | Should 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. |
| 4 | How does dunning comparison work when Stripe's retry schedule is configurable? | Reactor must match Stripe's configured schedule, not an independent one. |
| Risk | Impact | Mitigation |
|---|---|---|
| Clock drift | Critical | Reactor and Stripe alarms may not fire at exactly the same moment. Needs timing tolerance window. |
| Event correlation | Critical | Matching async events across two systems requires a correlation mechanism — no shared trigger to join on. |
| Proration model mismatch | High | Stripe creates proration line items; Reactor voids and replaces. Plan change comparison deferred until models align. |
| Base MR staleness | Medium | MR !9608 has merge conflicts with staging. Must rebase before shadow work proceeds. |
Two things to prove: the clock fires at the right time, the invoice shows the right amount. Shadow billing validates both on real traffic with zero risk. When they match, Reactor is ready to drive billing.