Stripe Metered Usage Billing for SaaS: Billing Meters Guide

08 Jul 2026 · Bank K.

Implement Stripe metered usage billing with the Billing Meters API — report meter events, build usage prices, and charge per-unit, with 2026 code.

If you’re billing for API calls, AI tokens, emails sent, or gigabytes processed, flat subscriptions don’t fit. You need Stripe metered usage billing — charge customers based on what they actually consume during a billing period. As of 2026 this runs on Stripe’s Billing Meters API, which replaced the old usage records approach and is the only supported path for new metered prices.

This guide covers the full loop: create a meter, attach a usage-based price, report meter events from your app, and let Stripe aggregate and invoice. Code targets the current Stripe API (the legacy subscriptionItem.createUsageRecord API was removed in 2025-03-31.basil and later — don’t follow old tutorials that use it).

How metered billing works now

The mental model has three pieces:

  1. A Meter — a named counter, like api_requests or tokens_used, that defines how usage aggregates (sum, count, last value) and which event field identifies the customer.
  2. A usage-based Price — a recurring price linked to that meter, with per-unit or tiered pricing.
  3. Meter events — the data you send Stripe each time usage happens. Stripe aggregates these asynchronously and invoices the total at the end of the period.

The big improvement over the old usage-records system: Billing Meters support high-throughput ingestion, don’t require a subscription to exist before you report usage, and a single meter can track usage across many customers. You report events with a customer identifier in the payload, and Stripe sorts it out.

Step 1: Create a billing meter

Define the meter once. The key choices are the aggregation formula and the event_payload_key that maps an event to a customer.

const meter = await stripe.billing.meters.create({
  display_name: 'API Requests',
  event_name: 'api_request',
  default_aggregation: { formula: 'sum' },
  customer_mapping: {
    event_payload_key: 'stripe_customer_id',
    type: 'by_id',
  },
  value_settings: {
    event_payload_key: 'value',
  },
});
  • event_name is what you’ll send when reporting usage (api_request).
  • default_aggregation.formula can be sum (add up value across events — typical for “5 requests” each), count (number of events), or last (latest value, useful for gauges like seats).
  • customer_mapping.event_payload_key tells Stripe which field in your event payload holds the Stripe customer ID.
  • value_settings.event_payload_key is the field holding the numeric amount of each event.

Step 2: Create a usage-based price tied to the meter

Now create a recurring price that bills against that meter. Set usage_type: 'metered' on the recurring config and point it at the meter:

const price = await stripe.prices.create({
  product: 'prod_apiPlan',
  currency: 'usd',
  unit_amount: 2, // $0.02 per request, in cents
  recurring: {
    interval: 'month',
    usage_type: 'metered',
    meter: meter.id,
  },
});

For tiered pricing (first 1,000 requests free, then $0.01 each, etc.), use billing_scheme: 'tiered' with a tiers array instead of a flat unit_amount. Stripe applies the tiers automatically based on aggregated usage.

Step 3: Subscribe the customer

A metered subscription looks like a normal one — you just add the metered price. Notice there’s no quantity: usage comes from meter events, not the subscription item.

const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: price.id }],
});

You can combine a flat base fee (a standard licensed price) with a metered price in the same subscription — that’s the classic “platform fee + usage” model. Just include both prices in items.

Step 4: Report meter events as usage happens

This is the part your application code does on every billable action. Send a meter event with the customer identifier and the value:

await stripe.billing.meterEvents.create({
  event_name: 'api_request',
  payload: {
    stripe_customer_id: customer.id,
    value: '1',
  },
  identifier: crypto.randomUUID(),
  timestamp: Math.floor(Date.now() / 1000),
});

A few things that matter:

  • The payload keys must match what you configured: stripe_customer_id for the customer mapping and value for the amount.
  • identifier is for idempotency. Stripe enforces uniqueness within a rolling window of at least 24 hours, so if you retry a failed request with the same identifier, it won’t double-count. Use a UUID per logical event.
  • timestamp must be recent — Stripe rejects events too far in the past or future. Report close to when usage actually occurred.

If you’re billing high volume, don’t make a synchronous Stripe call on every single request in the hot path. Buffer usage locally and flush batches, or use a queue. Stripe ingests meter events fast, but you don’t want your request latency tied to it.


Building usage billing into an MVP? Beag gives you authenticated users plus Stripe billing wired together, so the customer-to-subscription mapping metered billing depends on is already handled. You focus on reporting usage, not plumbing accounts.


Step 5: Verify usage with meter event summaries

Because aggregation is asynchronous and eventually consistent, you shouldn’t read usage back from your own counters and assume Stripe agrees. Check what Stripe actually recorded with meter event summaries:

const summaries = await stripe.billing.meters.listEventSummaries(
  meter.id,
  {
    customer: customer.id,
    start_time: periodStart, // unix seconds
    end_time: periodEnd,
  }
);

For hour granularity, start and end times must align to hour boundaries; for day, they must align to UTC day boundaries. This endpoint is what powers an in-app “usage this period” dashboard — read from Stripe’s aggregated view, not your raw logs, so your numbers match the invoice.

Tradeoffs to weigh

Metered billing is powerful but adds real complexity over flat plans:

  • Revenue is unpredictable for both you and the customer. Customers dislike surprise bills — add usage alerts or spending caps in your app so nobody gets shocked.
  • Eventual consistency means your live counter and the invoice can briefly disagree. Always reconcile against meter event summaries before showing authoritative numbers.
  • Reporting reliability is on you. If your app crashes before reporting an event, that usage isn’t billed. Buffer durably (a database row, then flush) rather than holding events only in memory.
  • Proration and mid-cycle changes get fiddly when a base fee and metered usage coexist. Test plan changes thoroughly.

For many indie SaaS products, a simple tiered flat plan is easier to reason about and sell than pure usage billing. Reach for metered billing when consumption genuinely varies a lot per customer — AI APIs, infrastructure, messaging — and a flat price would either scare away light users or undercharge heavy ones.

Wrapping up

The metered flow is: create a meter with an aggregation formula and customer mapping, create a usage_type: 'metered' price linked to it, subscribe the customer with no quantity, report meterEvents with an idempotent identifier as usage happens, and reconcile with meter event summaries before showing or trusting numbers. The legacy usage-records API is gone — build on Billing Meters.

If you’d rather get auth and Stripe billing wired up first and add metering on top, Beag adds authentication and payments to your MVP in minutes →. See also our Stripe subscriptions tutorial for the base recurring setup.

About the Author
Bank K.

Bank K.

Serial entrepreneur & Co-founder of Beag.io

Founder of Beag.io. Indie hacker building tools to help developers ship faster.

Ready to Make Money From Your SaaS?

Turn your SaaS into cash with Beag.io. Get started now!

Start 7-day free trial →