DEV Community

Cover image for How to implement usage-based billing for a SaaS product
Flexprice
Flexprice

Posted on Originally published at flexprice.io

How to implement usage-based billing for a SaaS product

One customer logs in twice a month. Another burns through a million API calls a day. Both pay the same, because the plan was priced when every account looked roughly alike. Finance wants the invoice to reflect reality, engineering wants pricing out of the request path, and billing ends up on whoever touched it last.

Usage-based billing fixes the pricing mismatch and introduces metering, aggregation, entitlements, credits, and invoice generation as new systems to get right. Here's the order to build them in.

What does implementing usage-based billing involve?

Five things: choosing a usage metric that tracks the value your product delivers, building a metering pipeline that captures every billable event exactly once, modelling the pricing on top of the aggregated usage, connecting billing to invoicing and payments, and exposing the meter so customers can see what they're spending. Everything after that is edge cases, and there are more of them than the first version assumes.

1. Choose the usage metric

Charge for the action that carries value, not the action that's easiest to count.

A video platform that bills per upload prices a ten-second clip the same as a one-hour recording. Billing per minute rendered tracks both the cost of serving the request and the value the customer got. That alignment is what makes a price defensible when a customer asks why their bill moved.

What this usually looks like by product type:

  • AI products: tokens processed, inference calls, or model seconds.
  • Cloud and data platforms: stored gigabyte-months, compute seconds, egress.
  • API products: requests, often segmented by endpoint class.
  • Agent platforms: sessions, runs, or distinct agents active in a period.

Then decide the granularity. Discrete counting records each event. Aggregate measurement records a volume over a window, such as GB-months. The two produce different event schemas, so pick before you instrument.

Say the price in the customer's terms. "$1 per 1,000 API calls" is checkable. A composite unit nobody can compute by hand generates support tickets for as long as it exists.

2. Build the metering pipeline

The pipeline has one job: every billable action becomes exactly one event, attributed to the right customer, with the properties needed to price it.

Capture at the source. Emit from the service that performs the work, not from a log parser downstream. Parsing logs for revenue data means a logging change silently becomes a billing change.

Carry an idempotency key. Every event gets a stable identifier from the emitter. A retried request that reuses the identifier resolves to one billable event. Without it, retries inflate invoices in a way that's invisible until a customer complains.

Normalize the timestamp. UTC, ISO 8601, set by the emitter rather than the receiver. Receiver timestamps make late events land in the wrong billing period.

Enrich before storing. Attach customer ID, project or environment tags, and the feature key at emit time. These are the dimensions you'll later want to price by, group by, or show in a customer dashboard.

{
  "event_name": "render.minutes",
  "external_customer_id": "cust_123",
  "properties": {
    "minutes": 42,
    "project": "proj_88a1",
    "resolution": "1080p"
  },
  "event_id": "evt_0b41f7",
  "timestamp": "2025-10-31T09:12:00.000Z",
  "source": "render-worker"
}
Enter fullscreen mode Exit fullscreen mode

Keep raw events. Roll-ups are derived. Keeping the raw stream is what lets you answer a dispute, re-run a period after fixing a bug, and backtest a pricing change against real history.

Handle volume. High-frequency emitters should batch. If usage already flows through Kafka, a webhook, or a database, route from there rather than instrumenting twice.

In Flexprice this side is a metered feature: an event name plus an aggregation, defined once, with the raw events queryable behind it.

3. Model the pricing

Pricing logic converts an aggregated quantity into a charge. The models below cover most SaaS and AI products, and they compose.

Pay as you go

Customers pay per unit consumed with no commitment. Simple to explain, low friction at signup, and revenue that tracks adoption directly. The tradeoff is revenue that's hard to forecast, since there's no floor.

Tiered or volume pricing

The per-unit rate drops as volume grows. For example 0 to 1M calls at $0.002, 1M to 10M at $0.0015, and lower beyond that. This works when your own unit costs fall with scale.

Decide early which of the two tier behaviours you mean, because they bill differently on identical usage. Volume applies a single rate to the entire quantity, chosen by the tier the total lands in. Slab, also called graduated, splits usage at each boundary and prices every slice at its own rate. Against tiers of 0 to 10,000 at $0.0010, 10,001 to 50,000 at $0.0008, and 50,001 to 100,000 at $0.0006, a customer making 65,000 calls owes $39 under volume and $51 under slab.

In Flexprice this is a price with billing_model: TIERED and a tier_mode of VOLUME or SLAB. The volume tiered doc works through the volume case with that same example.

Hybrid

A fixed base fee plus a usage component. A customer pays $100 a month covering 100K calls, then $0.001 per call beyond that.

Hybrid gives you a revenue floor and gives the customer a predictable baseline. It's the most common shape in production SaaS pricing, and the one that puts the most pressure on proration, because a mid-cycle plan change has to split both the fixed and the variable side. Hybrid pricing is a worked example modelled on Resend: one plan carrying a recurring charge, usage charges, and entitlements that gate features per tier.

Credit wallets and prepaid bundles

Customers buy credits up front and consumption draws the balance down. One credit might equal 1,000 tokens or one GPU minute.

Wallets suit workloads where daily consumption swings hard, because the customer controls exposure by controlling top-ups. They also need rules: expiry, auto top-up thresholds, and a priority order when promotional and purchased balances coexist. Prepaid and promotional credits covers the balance types.

Commitments and discounts

Enterprise deals involve negotiated terms: an annual minimum, a volume discount, or bonus credits against a committed spend. These are contract terms the billing system has to hold as data, including the effective dates, because they'll be renegotiated and you'll need to know which version applied to which invoice.

4. Connect billing, invoicing, and payments

An invoice should list each metric, the units consumed, and the resulting cost, with every line traceable to the events behind it. That traceability is what turns a disputed charge into a two-minute answer.

Connect the payment gateway so collection is automatic, and add retry logic for failed payments so recovery doesn't depend on someone noticing. Revenue recognition needs the same care: hybrid models produce overlapping periods where the fixed fee and the usage charge recognize differently.

5. Make the meter visible

Real-time usage views and threshold alerts prevent the surprise invoice, which is the main churn driver in consumption pricing. Alerting at a percentage of typical usage gives a customer time to act rather than time to be annoyed.

Describe units in the customer's language. "100 messages processed" lands better than "100 API calls made" for a non-technical buyer, even when they're the same event.

Internal observability matters just as much. Track events from ingestion through to invoice so you can see where a count dropped, which is how revenue leakage gets found before an auditor finds it.

6. The edge cases that arrive with scale

Late and out-of-order events. Decide a tolerance window and what happens to events that miss it. Both choices have revenue consequences, so make them explicitly.

Duplicates. Covered by idempotency keys if you set them at step 2. Retrofitting them later means a period where you can't fully trust the totals.

Mid-cycle plan changes. Versioned prices with rate history, plus automatic proration. Without stored rate history you can't reproduce an old invoice, which is a problem the first time someone asks you to.

Multi-tenant attribution. One account, several teams or environments. Customers want their own breakdown, so the dimension has to be on the event, not inferred later.

Forecasting. Variable revenue is harder to predict. Usage history is the input, which is another reason to keep the raw stream.

Build, buy, or open source

Building in-house gives full control and costs you the engineers you hired for the core product, permanently. The edge cases above are not a one-quarter project, and they don't stop arriving.

Buying a closed platform is faster and trades flexibility for it. The constraint shows up when your pricing model moves past what the vendor modelled, and the workaround is custom code that reimplements part of the billing system anyway.

Open source sits between them. You start from a working engine and keep the ability to read and change it.

Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud. Flexprice offers three deployment options, and all three run the same engine. Because Flexprice is open source and self-hostable, usage and revenue data can stay entirely inside your own infrastructure and never reach a vendor's cloud.

How to roll it out

Prove the chain end to end on one metric before extending it across the product.

Instrument a single billable event and get it flowing. Aggregate it on a period. Simulate pricing against real historical usage before committing to a number. Ship a usage view so customers can see the meter from the first day it runs. Then pilot on one feature or one customer tier, watch how the totals hold up under real volume, and expand from there.

Clone the repo and push your first event through it: github.com/flexprice/flexprice.

Top comments (0)