Context
Leadership read monthly recurring revenue off an internal subscribers table. Finance read it off Stripe. The two numbers did not match and nobody knew which one was right.
The problem
- Cancelled and lapsed subscriptions stayed marked active internally, inflating the number.
- At least one customer was paying every month with no internal record at all, so they could not log in or book service.
- Payment links had been created ad hoc over time, many with no metadata saying which plan they belonged to.
Architecture
- 01Events drive stateSubscription status in the app is derived from Stripe lifecycle events (created, paid, failed, cancelled), not edited by hand.
- 02Create on first paymentA recurring payment from an unknown customer now creates the member record, so a paying customer can never be invisible to the app again.
- 03Reconcile on a scheduleA scheduled job compares every active record with the processor and reports drift instead of silently correcting it.
- 04Paid means immutableThe invoice builder refuses to edit an invoice once it is paid. Corrections become a new document with a reference to the old one.
Security model
- Webhook payloads are verified with the processor's signing secret before they touch any record.
- Restricted API keys scoped to the operations the sync needs, not the account's full secret key.
- No card data ever touches the application. Checkout and card storage stay with the processor.
Tasks now automated
- Updating subscription status from payment events
- Creating the member record on a first recurring payment
- Comparing every active record with the processor on a schedule
- Grouping revenue by plan from payment-link metadata
Time returned
- Monthly to 0
- Hand reconciliations
- 35%
- Revenue overstatement removed
- 0
- Paying customers invisible to the app
The spreadsheet comparison is replaced by a scheduled job that reports drift.
Measured: internal figure versus processor figure on the day of the fix.
First recurring payment now creates the record automatically.
Time figures are estimates per unit of work, based on the manual steps the build removed. Every other number on this page is measured.
Outcome
Reported recurring revenue now matches the processor. The correction was a 35% reduction, which changed planning decisions that had been made on the old figure.
Every payment link carries its plan, so revenue can be grouped by product without guesswork.
Two systems that both claim to know the answer means neither does. Pick the one that moves money and derive everything else from it.