Stripe Billing Meters vs a usage ledger: when you need both

If you bill on Stripe, Billing Meters are the obvious place to send usage. A separate usage ledger isn't always needed. Here's what each one does, using only what Stripe's docs say (checked 2026-10-14), and when running both makes sense.

What Stripe Billing Meters do well

Worth knowing: Stripe's own usage-recording page now says Billing Meters suit teams already billing with them, and recommends Metronome for new integrations (Stripe: record usage for billing).

Where a separate usage ledger helps

When you need both

How Usage Lab sits in front of Stripe

Usage Lab is a small usage ledger. You POST /api/v1/usage with an Idempotency-Key. Each event is stored once per key: a replay returns "duplicate": true and a different payload under the same key returns 409. Meters aggregate with sum, count or unique_count. Events up to 30 days late land in the month they happened, and rollups are recomputed from raw events.

Stripe push: dry-run / coming soon. Each rollup already tracks exported_quantity and pending_export_quantity, but Usage Lab doesn't send meter events to Stripe yet. For now, read the rollup and send the quantity yourself.

# Usage Lab: record one event (sum meter "llm_tokens")
curl -X POST https://usage-lab.marc-cf2.workers.dev/api/v1/usage \
  -H "Authorization: Bearer $USAGE_LAB_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: chatcmpl-9x2a" \
  -d '{"meter":"llm_tokens","subscription":"cus_123","value":1250,"timestamp":"2026-10-14T08:00:00Z"}'
# 201 {"event":{"status":"created",...},"duplicate":false}
# same key + same payload again -> 200 "duplicate": true
# same key + different payload -> 409 idempotency_conflict

# Usage Lab: monthly quantity per subscription (recomputed from raw events)
curl "https://usage-lab.marc-cf2.workers.dev/api/v1/meters/llm_tokens/rollups?month=2026-10&subscription=cus_123" \
  -H "Authorization: Bearer $USAGE_LAB_KEY"

Response shape:

{
  "meter": { "key": "llm_tokens", "aggregation": "sum", ... },
  "month": "2026-10",
  "computed_at": "...",
  "total_quantity": 1250,
  "rollups": [{
    "subscription": "cus_123",
    "quantity": 1250,
    "event_count": 1,
    "exported_quantity": 0,
    "pending_export_quantity": 1250,
    "exported_at": null
  }]
}

And this is Stripe's meter event call, copied from Stripe's API reference. Use your own event_name, and a stable identifier so a retry doesn't double count:

# Stripe's API (from Stripe's docs), not Usage Lab
curl https://api.stripe.com/v1/billing/meter_events \
  -u "<<YOUR_SECRET_KEY>>" \
  -d event_name=ai_search_api \
  -d "payload[value]=25" \
  -d "payload[stripe_customer_id]={{CUSTOMER_ID}}" \
  -d identifier=identifier_123

Try the full flow on the live demo or read the API docs.

Sources