WritingStripe Metered Billing: Legacy Usage Records and Migration Notes — LinkIT
4 min readmetered-billing, stripe, saas-billing, usage-based-pricing

Stripe Metered Billing: Legacy Usage Records and Migration Notes

Understand an existing Stripe Usage Records integration, its version limits, and the migration to Billing Meters for current usage-based billing.

A usage-based billing system needs to record billable activity, report it to Stripe, and reconcile the resulting charges. This walkthrough describes the legacy Usage Records integration. Stripe removed these APIs in version 2025-03-31.basil; the examples below require an older compatible API version. For new integrations, use Billing Meters and follow Stripe’s migration guide.

How the Legacy Usage Records Integration Works

Stripe separates the concern of counting usage from the concern of charging for it. You report usage events throughout the billing period; Stripe aggregates them and includes the total on the invoice at period end.

In the legacy integration, the core objects are:

  • Product — what you are selling (e.g., "API Compute Units")
  • Price — the pricing config attached to the product, set to usage_type: metered
  • Subscription — the customer's active subscription referencing the metered price
  • Usage Record — the events you push during the billing period

Setting the price's aggregate_usage field controls how Stripe calculates the billable total: sum adds every event value, max takes the peak, and last_during_period takes the final reported value.

Step 1: Create a Metered Price in Stripe

When creating the price via API or Dashboard, set:

usage_type: metered
billing_scheme: per_unit   # or tiered
aggregate_usage: sum

For tiered pricing — where the per-unit rate drops at higher volumes — use billing_scheme: tiered and define your tiers. Tiered metered pricing requires you to also set tiers_mode to either graduated (each tier rate applies only to units in that tier) or volume (the rate of the tier reached applies to all units).

Step 2: Subscribe the Customer

Create a subscription with the metered price as a line item. The subscription's items[0].id is the subscription_item_id you will reference every time you report usage.

Store this ID in your database against the customer record. You will need it on every usage report call.

Step 3: Report Usage Events

Post usage to Stripe via the Usage Records API:

POST /v1/subscription_items/{subscription_item_id}/usage_records
{
  "quantity": 42,
  "timestamp": 1700000000,
  "action": "increment"
}

action: increment adds to the running total. action: set replaces the total — useful for gauge-style metrics like active seats.

High-Volume Aggregation

Sending one API call per user action can increase request volume and latency. Accumulate usage in your application layer — Redis is a practical choice — and flush aggregated totals on a short interval (every minute or every hour depending on your volume). Size and monitor batches against the API’s rate limits and your reporting requirements.

Step 4: Show Reported Usage to Customers

Customers need a way to track usage and expected charges during the billing period. Fetch the current period's usage from Stripe and surface it in your product dashboard:

GET /v1/subscription_items/{subscription_item_id}/usage_record_summaries

This endpoint returns period-to-date totals. Show this data alongside the projected cost so customers can make informed decisions before the invoice arrives.

Step 5: Handle Invoicing and Webhooks

Review invoice creation and finalization behavior for your billing configuration. Receiving an invoice webhook does not by itself pause automatic collection; configure an explicit review or finalization workflow if a human must approve a charge.

Also listen to invoice.payment_failed so your dunning flow can kick in immediately. Failed payments on metered invoices require the same retry and notification logic as any subscription payment failure.

Common Implementation Pitfalls

  • Timestamp drift: Usage records with future timestamps are rejected. Always use server-side UTC timestamps, never client-supplied values.
  • Missing idempotency keys: If your flush job retries on failure, duplicate events will inflate the customer's bill. Set an idempotency key derived from your internal event batch ID.
  • Delayed reporting: Stripe's billing period closes at the subscription's anchor date. Do not assume late usage will move to the next invoice; check the reporting window and invoice-finalization rules for the API and billing setup you use. Build monitoring that alerts if usage reports fall behind.
  • No usage floor: Some metered plans carry a minimum monthly commitment. Model this as a flat-fee line item on the same subscription, not as a price floor on the metered price — it gives you cleaner reporting and a clearer customer invoice.

Reconciliation

Compare your internal usage logs against the usage summaries Stripe returns on a defined schedule. Investigate discrepancies before invoices are finalized where possible.

Discuss usage reporting and billing reconciliation with LinkIT.