Mode
Rumble Built, Inc.

We Built the Same Billing Layer Five Times. Here Is What We Learned.

Five products, five Stripe integrations, one slowly dawning realization. A post-mortem on duplication — and the rule we now use to decide what belongs on the platform.

If you want to know whether a company has a platform or a story about a platform, look at how many times it has implemented billing.

We had five.

How it happened

It was not carelessness. Each product had a real reason to be different. CapturedIt needed personal and team scopes with structurally separate keys. Premodoro needed Stripe on the web and Apple in-app purchase on Safari. HuniDu needed per-pillar subscriptions a household could turn on and off. Bread & Butter needed seats, roles, and an invoice that a finance team could read. nMyCity needed almost nothing — until it needed a tenant.

Five legitimate shapes. Five implementations. Five sets of webhook handlers that each fixed the same race condition on a different Tuesday.

The moment it became obvious

The tell was not the code. It was the dashboards. We had five places to look when someone said "I paid and I still can't see the feature." Each one answered a slightly different version of the question.

That is when a shared layer stops being an engineering nicety and becomes an operations requirement. You cannot run support across a portfolio when every product has a private definition of "entitled."

The rule we use now

Something goes on Rumble Rails when all three are true:

  1. At least two products need it. One product is a feature. Two is a pattern. Five is a tax.
  2. Getting it wrong costs money or trust — billing, identity, telemetry, provider failover. Nobody churns because your empty state is ugly. People churn because they were charged twice.
  3. It has nothing to do with what the product is for. Nobody chooses a household app for its Stripe webhook handling. If a capability is not part of why a customer picks the product, it belongs on the rails.

Everything else stays in the product, where it can be opinionated.

What a shared billing line looks like

On the Rail Map, billing and entitlements is one line. In practice it is a small set of promises every product can rely on:

  • One definition of a plan, a seat, and a limit — and the UI, the API, and the invoice all read from it.
  • Webhooks handled once, idempotently, with the race conditions already paid for.
  • Metering that works the same whether the unit is a save, a widget, or a seat.
  • A single place to answer "what is this account entitled to right now, and why."

Premodoro runs fully on it today. The others are being connected one at a time, and the map on the platform page tracks which. We would rather show a dashed line than claim a solid one.

The uncomfortable part

Building the fifth billing integration felt productive. It shipped a product. Pulling the shared layer out felt like a detour. It shipped nothing a customer could see.

It was the most leveraged work we did this year.

Back to Field Notes