DEV Community

kttherealest
kttherealest

Posted on

How to Actually Set Up Stripe's Billing Meters in 2026 (Most Tutorials Are Wrong)

If you've searched for "Stripe usage-based billing" or "Stripe metered pricing" recently, there's a good chance you found a tutorial referencing stripe.subscriptionItems.createUsageRecord(). That approach is dead. Stripe fully deprecated it in 2025, and you can no longer even create a new metered price the old way in the Dashboard.

I found this out mid-build, while working on a usage-based billing starter kit. Here's the current, correct way to do it.

What actually changed

The old model: attach a metered price directly to a subscription item, then report usage against that specific item's ID.

The new model: create a Meter object first (a named, reusable event definition), attach a price to that meter, and report usage against the customer, not a subscription item.

This is a meaningful simplification once you know it — you no longer need to track a subscription item ID in your database at all.

Step 1: Create the Meter

In the Stripe Dashboard: Billing → Meters → Create meter.

You'll set:

  • A display name (for your own reference)
  • An event name — a string you choose, e.g. api_requests. This is the identifier your code will use to report usage, so pick something meaningful and stable.
  • Aggregation: usually "Sum" (adds up every reported value over the billing period).

Step 2: Create a metered Price attached to that Meter

Product catalog → Add product → when setting the price, choose "Usage-based" (not "Standard"), and select the Meter you just created. Set your per-unit price.

Copy the resulting Price ID — you'll use it for Checkout.

Step 3: Create a Checkout Session (no quantity!)

This is a small but important detail: metered prices don't take a quantity. If you pass one, Stripe rejects the request.

\js
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
line_items: [{ price: process.env.STRIPE_PRICE_ID }], // no quantity
success_url: '...',
cancel_url: '...',
});
\
\

Step 4: Report usage

This is the actual replacement for the old createUsageRecord call:

\js
await stripe.billing.meterEvents.create({
event_name: 'api_requests', // must match what you set up in Step 1
payload: {
value: '1',
stripe_customer_id: customerId,
},
});
\
\

Note it's keyed to stripe_customer_id, not a subscription item ID. Stripe aggregates these events per meter, per customer, per billing period, and calculates the invoice automatically.

A reliability note

If this call fails (network blip, wrong event name, etc.), decide deliberately what happens to the user's request. In my case, I chose to still fulfill the request and just log the error — a billing-reporting failure shouldn't break the actual feature. But that does mean a failed report is usage that goes unbilled silently. For anything handling real revenue, you want a retry queue (store failed events, retry on a schedule) rather than a bare console.error.

Common gotchas

  • Test-mode and live-mode Meters are separate objects. Don't assume the Meter you created in test mode carries over — you'll need to create a live one too before going to production.
  • The event name is exact-match. A typo between what you configured in the Dashboard and what your code sends means silently-uncounted usage, not an error.
  • Webhooks still work the same way for tracking subscription status (customer.subscription.updated, etc.) — that part of the integration didn't change.

Working example

I built this into a full starter kit (Next.js + Prisma + NextAuth + Stripe) if you want to see it wired together end-to-end, including the free-tier gating before metered billing kicks in: https://whop.com/table-export-tools/meter-usage-based-billing-saas-starter-stripe-meters/

Top comments (0)