Usage-based billing is where SaaS pricing is heading — every AI product bills by tokens, requests, or seats-plus-usage. It's also where billing bugs get expensive: overcharge and you refund with apologies; undercharge and the money is simply gone. Here's how we implement metered billing with Stripe in Next.js, based on production systems we've shipped at PolarSoftBD.
The three usage-based models (pick one deliberately)
- Pure metered — pay per unit consumed ($0.01 per API call). Simple to reason about, revenue is volatile.
- Base + overage — subscription includes N units, meter beyond that ($29/mo includes 10k calls, then $0.005/call). The most common model, and usually the right one.
- Prepaid credits — customers buy credit packs that draw down. Best cash flow, best for AI products. (We cover this in depth in Building AI Credit Systems.)
Stripe's modern metering: the Meters API
Stripe replaced legacy usage records with Billing Meters. You define a meter once, then stream events at it:
// Report usage — call this from your API middleware/job
await stripe.billing.meterEvents.create({
event_name: "api_requests",
payload: {
stripe_customer_id: customer.stripeId,
value: "1",
},
// identifier makes retries safe — Stripe dedupes on it
identifier: `req_${requestId}`,
});
Then a price attached to that meter does the aggregation and invoicing automatically. The identifier field is your idempotency key — always set it, because your job queue will retry.
The architecture that survives production
Do not call Stripe synchronously in your request path. The pattern that works:
API request → increment local counter (Redis/Postgres)
↓ (batched, every 1–5 min)
aggregation job → stripe.billing.meterEvents
↓
Stripe invoices at period end
Local-first metering gives you three things Stripe alone can't:
- Real-time quota enforcement — you can't ask Stripe "how much has this customer used?" on every request; you need the number in Redis
- An audit trail — when a customer disputes an invoice, you show them per-request logs
- Resilience — if Stripe is briefly down, usage queues instead of vanishing
// Quota check in middleware — reads local state, never Stripe
const used = await redis.incrby(`usage:${customerId}:${period}`, cost);
if (used > plan.includedUnits + plan.hardLimit) {
return new Response("Quota exceeded", { status: 429 });
}
Base + overage in practice
Model it as a subscription with two items: a flat price and a metered price with included units handled via graduated tiers:
await stripe.subscriptions.create({
customer: customerId,
items: [
{ price: "price_base_29" }, // flat $29/mo
{ price: "price_metered_overage" }, // tiered: first 10k @ $0, then $0.005
],
});
Configure the metered price with graduated tiers: tier 1 up_to: 10000 at $0, tier 2 up_to: inf at $0.005. The "free included units" live in Stripe's tier config, not your application code — one less thing to get wrong.
The four failure modes to design against
- Double reporting — job retries send the same usage twice. Fix: deterministic
identifierper usage batch. - Clock skew at period boundaries — usage from 23:59 lands in next month's invoice. Decide policy (bill in arrears is fine), document it, be consistent.
- Unbounded exposure — a customer's runaway script generates a $40,000 invoice overnight. Fix: hard limits in middleware + billing alerts via
billing.alert.triggeredwebhooks. - Refund asymmetry — refunds and plan downgrades mid-period. Proration on metered items doesn't work like flat items; test this path explicitly before launch.
What customers see (this decides churn)
Usage-based billing dies without transparency. Ship these with v1, not "later":
- A live usage dashboard (read from your local counters, not Stripe)
- Email alerts at 75% / 90% / 100% of included usage
- Upcoming-invoice preview via
stripe.invoices.createPreview
Surprise invoices are the #1 cause of usage-billing churn and disputes. An informed customer at 90% upgrades; a surprised one at 130% files a chargeback.
Ship it without the six-week debugging arc
Metered billing touches your API layer, your data model, your background jobs, and your customer dashboard — it's a system, not a feature. We build it as one:
- Stripe integration service → — subscription and metered billing implementations from $250, fixed price
- Book a free strategy call → — bring your pricing page, we'll map it to a billing architecture
Related: Building AI Credit Systems for SaaS · Complete Lemon Squeezy Webhook Guide