Skip to main content

How to Add Usage-Based Billing with Stripe Meters 2026

·StarterPick Team
stripeusage-billingstripe-meterssaas-boilerplatebilling2026

TL;DR

Stripe Meters (GA in 2024) is the cleanest way to implement usage-based billing in 2026. Create a meter → report usage via stripe.billing.meterEvents.create() → attach a metered price to a subscription → Stripe handles aggregation and billing at period end. Works for AI tokens, API calls, storage, emails sent — any countable resource.

Key Takeaways

  • Stripe Meters: New GA feature — simpler than legacy subscriptionItems.createUsageRecord
  • Setup: Create meter in dashboard → create metered price → subscribe user with meter
  • Report usage: stripe.billing.meterEvents.create() for each event (or batch)
  • Query usage: stripe.billing.meters.listEventSummaries() to show customers their usage
  • Billing: Stripe aggregates at end of billing period (month), charges automatically

Step 1: Set Up Stripe Meter

// Run once to create the meter (or do it in Stripe dashboard):
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// Create meter for AI token usage:
const meter = await stripe.billing.meters.create({
  display_name: 'AI Tokens',
  event_name: 'ai_tokens_used',        // Event name you'll report
  default_aggregation: { formula: 'sum' },
  customer_mapping: {
    event_payload_key: 'stripe_customer_id',  // Field in event payload
    type: 'by_id',
  },
  value_settings: { event_payload_key: 'tokens' },  // Field for the count
});

console.log('Meter ID:', meter.id);
// Save this ID in your config/env: STRIPE_AI_TOKENS_METER_ID=mtrid_xxx
// Create metered price (attach to a product):
const price = await stripe.prices.create({
  product: 'prod_yourProductId',
  currency: 'usd',
  billing_scheme: 'per_unit',
  unit_amount: 2,  // $0.002 per token (0.2 cents)
  recurring: {
    interval: 'month',
    usage_type: 'metered',
    meter: process.env.STRIPE_AI_TOKENS_METER_ID!,
  },
});

console.log('Metered Price ID:', price.id);
// STRIPE_AI_TOKENS_PRICE_ID=price_xxx

Step 2: Subscribe Users to Metered Price

// lib/stripe-billing.ts:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function subscribeUserToUsagePlan(userId: string) {
  const user = await db.user.findUnique({ where: { id: userId } });

  // Create subscription with base price + metered price:
  const subscription = await stripe.subscriptions.create({
    customer: user!.stripeCustomerId!,
    items: [
      // Base monthly fee (optional):
      { price: process.env.STRIPE_PRO_BASE_PRICE_ID },
      // Metered usage (tokens):
      { price: process.env.STRIPE_AI_TOKENS_PRICE_ID },
    ],
  });

  await db.user.update({
    where: { id: userId },
    data: { stripeSubscriptionId: subscription.id },
  });
}

Step 3: Report Usage

// lib/usage-tracking.ts — report usage to Stripe Meters:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function reportAITokenUsage(
  stripeCustomerId: string,
  tokens: number
) {
  await stripe.billing.meterEvents.create({
    event_name: 'ai_tokens_used',  // Must match meter's event_name
    payload: {
      stripe_customer_id: stripeCustomerId,  // Must match customer_mapping key
      tokens: tokens.toString(),              // Must be a string
    },
  });
}

// Call this after each AI operation:
// In your AI route handler:
const result = await streamText({ model: openai('gpt-4o-mini'), ... });

result.onFinish(async ({ usage }) => {
  await reportAITokenUsage(
    session.user.stripeCustomerId,
    usage.promptTokens + usage.completionTokens
  );
});

Step 4: Show Usage to Customers

// app/api/billing/usage/route.ts:
export async function GET() {
  const session = await auth();
  const user = await db.user.findUnique({ where: { id: session!.user.id } });

  // Get current period usage from Stripe Meters:
  const summaries = await stripe.billing.meters.listEventSummaries(
    process.env.STRIPE_AI_TOKENS_METER_ID!,
    {
      customer: user!.stripeCustomerId!,
      start_time: Math.floor(new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime() / 1000),
      end_time: Math.floor(Date.now() / 1000),
      value_grouping_window: 'day',
    }
  );

  const totalTokens = summaries.data.reduce((sum, s) => sum + (s.aggregated_value ?? 0), 0);
  const costUsd = totalTokens * 0.002 / 1000;  // $0.002 per 1K tokens

  return Response.json({
    tokensUsed: totalTokens,
    estimatedCost: costUsd.toFixed(4),
    dailyBreakdown: summaries.data.map(s => ({
      date: new Date(s.start_time * 1000).toISOString().split('T')[0],
      tokens: s.aggregated_value,
    })),
  });
}
// components/billing/UsagePanel.tsx:
export function UsagePanel() {
  const { data } = useSWR('/api/billing/usage');

  return (
    <div className="space-y-4">
      <div>
        <p className="text-2xl font-bold">{data?.tokensUsed.toLocaleString()}</p>
        <p className="text-sm text-muted-foreground">AI tokens used this month</p>
      </div>
      <div>
        <p className="text-xl">${data?.estimatedCost}</p>
        <p className="text-sm text-muted-foreground">estimated charges</p>
      </div>
      <Progress value={(data?.tokensUsed / 1_000_000) * 100} className="h-2" />
    </div>
  );
}

Add Budget Limits (Optional)

// Prevent runaway costs with soft limits:
export async function checkAIBudget(userId: string): Promise<{ allowed: boolean; remaining: number }> {
  const user = await db.user.findUnique({ where: { id: userId } });
  const monthlyBudgetTokens = user?.plan === 'pro' ? 1_000_000 : 50_000;

  // Get current month usage from DB (faster than Stripe API):
  const used = await db.aiUsage.aggregate({
    where: {
      userId,
      createdAt: { gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1) },
    },
    _sum: { tokens: true },
  });

  const usedTokens = used._sum.tokens ?? 0;
  return {
    allowed: usedTokens < monthlyBudgetTokens,
    remaining: Math.max(0, monthlyBudgetTokens - usedTokens),
  };
}

Decision Guide

Use Stripe Meters if:
  → Billing per AI token, API call, email sent, or storage GB
  → Want Stripe to handle aggregation + billing automatically
  → New project — meters are the modern approach

Use legacy subscriptionItems.createUsageRecord if:
  → Existing Stripe integration before Meters GA
  → Gradual migration path needed

Use DB-only tracking (no Stripe Meters) if:
  → Just enforcing limits (no billing per usage)
  → Free tier with token caps, pro tier with unlimited

Find SaaS boilerplates with Stripe billing at StarterPick.

Comments