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)