← Insights
build

Usage-Based Billing: A Teardown of Where the Money Leaks

Metered billing sounds simple until you have to meter accurately, survive disputes, and not lose revenue to rounding and races. A teardown of every leak in the pipeline

The pricing page said "$0.002 per API call" and everyone nodded. It reads like a multiplication problem. Count the calls, multiply by the rate, send an invoice. A junior engineer could ship it in an afternoon, and one did.

Then a customer's invoice was 12% higher than their own logs said it should be, and they stopped paying while they investigated. A retry storm double-counted a million events. A rounding rule that seemed reasonable was quietly leaving four figures a month on the table across the customer base. The multiplication problem turned out to be a distributed systems problem wearing a pricing page as a disguise.

Usage-based billing fails at the seams between stages, not inside any single stage. Let's tear down the pipeline and find every place the money leaks.

The pipeline, stage by stage

Metered billing is four stages, and each one has a distinct failure mode:

  1. Ingestion — capturing that a billable thing happened.
  2. Aggregation — rolling raw events into billable quantities per customer per period.
  3. Rating — applying the price to the aggregated quantity, including tiers and rounding.
  4. Reconciliation — proving that what you billed matches what actually happened.

Teams build stage one, fake stage two with a SELECT COUNT(*), skip stages three's edge cases, and never build stage four. Then they wonder why the numbers don't hold.

Stage one: ingestion, where idempotency is not optional

A usage event is a claim that a billable action occurred. The first leak is that this claim can arrive zero times, once, or many times, and your revenue depends on it arriving exactly once in your ledger.

Networks retry. A client times out waiting for an acknowledgment, resends, and now the same API call is two billable events. A queue redelivers on consumer crash. An at-least-once message bus — which is most of them — guarantees you'll see duplicates. If ingestion naively appends every event it receives, you over-bill, a customer audits, and you've turned a retry into a refund and a trust problem.

The defense is an idempotency key on every event, enforced at write time. The producer assigns a stable ID to each billable action — derived from the action itself, not generated fresh on each send. Ingestion writes events with that key as a unique constraint. A duplicate insert is rejected by the database, not by application logic you hope ran. The unique index is the dedup mechanism, because anything weaker has a race in it.

The second ingestion leak is late and out-of-order events. A meter on the customer's side buffers usage and flushes hourly, so an event timestamped 11:58pm on the last day of the month arrives at 12:04am the next month. Which period does it belong to? You must decide explicitly — bill by event timestamp, not arrival time — and you must keep a billing period open long enough to absorb stragglers before you finalize. Finalize too early and late events either get dropped (you eat the revenue) or land in the wrong month (the customer disputes).

Stage two: aggregation, where the COUNT(*) lie lives

The seductive shortcut is computing usage at invoice time with a live query: SELECT COUNT(*) FROM events WHERE customer = ? AND period = ?. It works in the demo. It fails in three ways under load.

It's not reproducible. Run the same query a day later and get a different number, because late events arrived or a cleanup job pruned old rows. You cannot defend an invoice you can't reproduce.

It's expensive and slow as event volume grows, and it runs at exactly the moment you can least afford a slow query — invoice generation across the whole customer base at once.

It can't express real pricing. Tiered rates, per-dimension caps, and committed-use discounts need pre-aggregated buckets, not a flat count.

The architecture that holds: aggregate continuously into immutable period buckets. As events land, roll them into per-customer, per-dimension, per-period counters. Treat each finalized bucket as a snapshot you never recompute — a frozen fact. The invoice reads buckets, not raw events. When a customer asks how you got a number, you show them the bucket and the events that composed it. Aggregation becomes auditable instead of a query you rerun and pray returns the same thing.

Stage three: rating, where rounding quietly drains revenue

Rating is multiplication, and multiplication is where teams lose money to math they didn't think was worth thinking about.

Rounding direction and timing compound. Round each event to the nearest cent and you've thrown away the fractional revenue on every single call — at a sub-cent unit price and millions of events, that's real money, gone, per month, forever. The fix is to rate on the aggregated quantity at full precision and round once, at the invoice line, with a stated direction. Never round per event. Where you round, and how many times, is a revenue decision, not a formatting detail.

Tier boundaries are off-by-one factories. "First 10,000 calls free, then $0.002" has to be unambiguous about whether the 10,000th call is free. Pick a convention, encode it in one place, and test the boundary explicitly. Tiering scattered across the rating code is how two customers at the same usage get two different bills.

Floating point doesn't belong near money. 0.1 + 0.2 is not 0.3 in a float, and across millions of operations that error accumulates into a number a customer will notice. Rate in integer minor units or a decimal type. This is not pedantry; it's the difference between an invoice that balances and one that doesn't.

Stage four: reconciliation, the stage nobody builds

This is the leak that hides the longest. Reconciliation is the job that proves, independently, that what you billed equals what happened. Most teams never build it, so they discover discrepancies when a customer does — the worst possible order.

Reconciliation runs as a scheduled pass that recomputes billable quantities from the event ledger and compares them against what the invoices actually charged. Any gap is an alert. A customer whose entitlement says metered but whose invoice shows zero usage is either getting a free ride or a meter is broken — both are findings you want before the invoice goes out, not after.

The strongest version reconciles against an external truth too. If the customer can see their own usage in your product, that number, the aggregated bucket, and the invoice line all have to agree. When they don't, you've found the leak before the customer turns it into a dispute.

Dispute handling: assume it, design for it

A customer will claim your number is wrong. Sometimes they're right. A billing system that can't answer "show me exactly what I'm paying for" loses the dispute by default, regardless of who's correct, because trust is the actual currency.

Design for the dispute up front:

  • Every invoice line traces to its events. A customer can drill from a charge to the aggregated bucket to the underlying events with timestamps. The audit answers itself.
  • Usage is visible before it's billed. A live usage dashboard turns end-of-month surprises into mid-month awareness. Disputes drop when there are no surprises.
  • Corrections are append-only adjustments, never edits. When you're wrong, you issue a credit as a new ledger entry. You never mutate a finalized invoice. The history stays intact, which is exactly what a finance team and an auditor need.

What fixed looks like

Every billable event carries an idempotency key, and the unique index — not hopeful application code — makes double-counting impossible. Late events land in the right period because you bill by event time and hold periods open for stragglers.

Usage is aggregated continuously into immutable buckets, so invoices read frozen snapshots instead of rerunning a COUNT(*) that returns a different answer each time.

Rating happens at full precision with rounding applied once, in a stated direction, on integer minor units — so the fractional cents that were silently draining stay in your revenue.

Reconciliation runs on a schedule and alerts on any gap between events, aggregates, and invoices, so leaks surface in a dashboard instead of a customer's email.

And every charge traces back to its events, so a dispute is a five-minute lookup instead of a forensic reconstruction that erodes the relationship.

./meter --ingest=idempotent --aggregate=immutable --rate=once --reconcile=daily

This is for you if

You're shipping usage-based or hybrid pricing on real volume — funded, with customers whose invoices are large enough that a 12% discrepancy ends the relationship. This is a $50k+ build: the metering pipeline, idempotent ingestion, immutable aggregation, precise rating, and the reconciliation job that proves the numbers. For platforms metering millions of events across many dimensions, with enterprise customers who audit their bills and a finance team that needs an airtight ledger, it's the $100k+ version where dispute traceability and external reconciliation are the whole game.

It's the right time if your invoices don't match your customers' own logs, if a retry has ever double-counted, or if you can't reproduce last month's usage number on demand.

This is not for you if you're on flat per-seat pricing with no metered component, or you're metering a handful of low-stakes events where a rounding error is rounding error and nobody audits. Off-the-shelf metered billing covers that. Build this when the meter is the product and the dollars are large enough that the seams matter.

< transmit >