Usage-based pricing for SaaS: the metering and billing patterns that work
How to ship usage-based billing in a SaaS without breaking trust: meter design, idempotency, hybrid plans, and the platform choice.
By the end of this guide you will have a working usage-based billing pipeline in a Next.js + Stripe SaaS: a meter that ingests events at API rate, a hybrid pricing plan that mixes a base subscription with consumption overages, idempotent event recording, an in-product usage view, and the safeguards that stop a billing bug from turning into a refund storm.
Usage-based pricing is no longer a fringe choice. In the 2025 OpenView and Metronome benchmarks, 85% of surveyed SaaS companies had adopted or were testing usage-based pricing, up from 28% in 2023. Companies on usage-based models report roughly 10% higher net retention and 22% lower churn than seat-based peers. The reason is simple: when revenue grows with the customer's success, the customer treats the spend as proportional, not as a fixed line they need to cut.
The trade-off is that you now sell something harder to deliver. A 1% metering error in a seat plan is invisible. A 1% metering error in a consumption plan is a finance ticket and a lost customer.
What you need before starting
- A SaaS already on Stripe (Subscriptions or Customer objects in place) or a comparable PSP. The patterns below are written against Stripe Billing because it is the most common starting point and ships Meters and Meter Events as first-class primitives.
- A clearly named billable unit. API call, token, seat-hour, row processed, generated image. If you cannot name it in three words, you are not ready to price it.
- An event source that already exists. Logs, queues, or DB writes that record the unit. Do not stand up a new event bus just to bill: instrument what is already running.
- A finance contact who will sign off on rounding rules, proration, refund policy, and tax. This is not optional for a B2B plan.
Step 1. Pick a pricing shape, not just a price
The shape that converts and renews best in 2026 is the hybrid plan: a base subscription that covers a generous included quota, plus an overage rate per unit beyond it. Most customers prefer this because they get a predictable base and only pay extra when they actually scale. Pure pay-as-you-go works for developer tools and infrastructure where buyers expect it; it tends to suppress trial-to-paid for everyone else.
Decide three numbers and write them down before you touch code:
- Base price per tier (Starter, Team, Business).
- Included quota per billing period at each tier.
- Overage unit price, ideally with one volume break above a threshold (so a customer who runs 10x the included quota does not pay 10x the overage).
Keep the model boring on day one. Tiered pricing with one overage axis is enough. Add per-feature meters only after the first model has been live for two billing cycles.
Step 2. Define the meter
In Stripe, a meter is the schema for an aggregated usage signal. You configure the meter once: an event name (for example api_request), an aggregation method (count or sum), and the field on the event payload that carries the numeric value.
// One-off, run from a server script
await stripe.billing.meters.create({
display_name: 'API requests',
event_name: 'api_request',
default_aggregation: { formula: 'count' },
customer_mapping: { event_payload_key: 'stripe_customer_id', type: 'by_id' },
value_settings: { event_payload_key: 'value' },
});Three rules at this step. First, one meter per billable dimension; do not multiplex two meanings into one event name. Second, the customer mapping is the contract that ties an event to an invoice line: if it is wrong, no usage will surface. Third, you will rename the event eventually; keep the meter id stable.
Step 3. Record events idempotently
This is the step that decides whether your model is trustworthy. Every meter event must carry an idempotency key derived from a stable property of the underlying action (request id, job id, message id), not from a timestamp. Stripe's Meter Events endpoint accepts an identifier field for this purpose, and a single endpoint allows up to 1,000 calls per second in live mode.
// In your request handler, after the work has succeeded
await stripe.billing.meterEvents.create({
event_name: 'api_request',
payload: {
stripe_customer_id: customer.stripeId,
value: '1',
},
identifier: requestId, // already unique upstream
timestamp: Math.floor(Date.now() / 1000),
});Send the event after the work succeeded, not before. A failed call you already billed for is the worst possible bug, because it produces invoices the support team cannot defend. Deduplicate at the ingestion boundary, and treat downstream queues as best-effort.
Step 4. Wire the meter to a price and a subscription
Create a Stripe Price with recurring.usage_type = 'metered' referencing your meter. Attach that price to the subscription alongside the base flat-fee price. The customer now has a subscription with two items: a flat-fee item that bills predictably, and a metered item that bills the aggregated usage at period close.
Tiered overage pricing is a property of the metered Price: configure tiers with a free included volume and an overage unit price. Stripe handles the math at invoice time.
Step 5. Show usage inside the product
This is the lever most teams ignore and the one that drives the largest retention gain. Render a usage widget in the dashboard with three things: current period consumption, included quota and percent used, and projected overage at end of period based on current run-rate. Pull the data from your own event store, not from Stripe; Stripe is the system of record for billing, not for live UX.
Send a notification at 75%, 90%, and 100% of included quota. Customers who never see a number are blindsided by their first invoice; customers who watch the number climb upgrade voluntarily. The same widget is also where you place an upgrade CTA when a customer starts running over the cap two cycles in a row.
Step 6. Verifying it works
- Event-to-invoice round trip. Send a controlled set of test events for a test customer in a Stripe test clock. Advance the clock to the period close. Confirm the invoice line matches your expected math to the cent.
- Duplicate replay. Send the same event twice with the same identifier. The second one should not increment the meter.
- Late events. Send an event with a timestamp 24 hours in the past. Confirm it lands in the right billing period and that any nightly close has not already locked the period.
- Customer mapping. Send an event with an unknown
stripe_customer_id. Confirm the event is rejected loudly and surfaces in your alerting, not silently dropped.
Common failures and fixes
Meter shows zero usage in production. Almost always a customer mapping bug: the field on the event payload does not match the meter's customer_mapping.event_payload_key. Stripe will accept the events but they bind to no customer. Add a server-side assertion that every outbound event carries a known customer id.
Invoice math is off by 1 to 5%. Race conditions between event emit and the work succeeding. Move the emit call to the success path only, and if you cannot, switch to a transactional outbox: write the meter event to a local table inside the same DB transaction as the business write, then a worker drains the table to Stripe with retries.
Stripe rate limits during traffic spikes. The 1,000 events per second cap is per-account, not per-customer. Stripe's own engineering blog covers the batching pattern: aggregate events client-side in a 1 to 5 second window, emit one Stripe event per (customer, meter, window) instead of one per request. You lose nothing on the invoice, you gain headroom of 100x or more.
Customer disputes a charge they cannot reconcile. Build the dispute path into the product, not into support tickets. Expose a CSV export of every event recorded for the customer in the disputed period. The support load drops by half once the customer can audit their own usage.
When to leave Stripe Billing for a dedicated platform
Stripe Billing carries a single-meter SaaS comfortably to mid-eight-figure ARR. The break points where teams move to Metronome, Orb, or open-source Lago are: more than three independent meters, contract-aware pricing (custom rates per enterprise customer signed in DocuSign), real-time entitlements (paywalls that need to know inside 50 ms whether a customer has quota left), or multi-PSP requirements where Stripe lock-in becomes a strategic risk. Below those break points, swapping platforms is engineering work that does not produce revenue.
Two cycles of live data are worth more than two months of platform shopping. Ship the boring hybrid plan on Stripe Billing, watch the invoices, then decide.
Going further
- Multi-tenant from day one covers the data architecture you want under any usage-based plan.
- The agency tax for the broader pricing context this fits into.
Sources
- Metronome, State of Usage-Based Pricing 2025
- OpenView, The State of Usage-Based Pricing
- Stripe Docs, Billing Meters API Reference
- Stripe Docs, Create a billing meter event
- Stripe Docs, Set up a pay-as-you-go pricing model
- Stripe Engineering, Scalable metered billing with Stripe (Edgee)
- OpenMeter, Usage Deduplication is Challenging
- Zenskar, Usage-Based Billing 2026 Guide
Studio
Start a project.
One partner for companies, public sector, startups and SaaS. Faster delivery, modern tech, lower costs. One team, one invoice.