Skip to content

Case study · Subscription services

Making the payment processor the source of truth for recurring revenue

Recurring revenue was being reported from an internal table that had drifted from what customers actually paid. We rebuilt the sync around payment events and found the dashboard overstated revenue by 35%.

35%
Overstatement found in reported recurring revenue
1
Source of truth (the processor), down from two
Auto
Member record created on first recurring payment
0
Mutations allowed on a paid invoice

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

  1. 01Events drive stateSubscription status in the app is derived from Stripe lifecycle events (created, paid, failed, cancelled), not edited by hand.
  2. 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.
  3. 03Reconcile on a scheduleA scheduled job compares every active record with the processor and reports drift instead of silently correcting it.
  4. 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

The spreadsheet comparison is replaced by a scheduled job that reports drift.

35%
Revenue overstatement removed

Measured: internal figure versus processor figure on the day of the fix.

0
Paying customers invisible to the app

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.

Next step

Tell us which system is slowing you down.

A 15 to 20 minute scoping call. If it is a fit, scope, price and timeline follow in writing.