Usage billing

How to implement usage-based billing with Stripe

Stripe can collect payment for metered products, but your app still needs runtime usage tracking, entitlement-backed access, and safe consumption logic. Licenzy helps connect checkout, entitlement state, and usage consumption.

Start free in test mode. Go live when you're ready.

Category: Integration Flow6 sections

Introduction

Usage-based billing means customers pay based on what they consume instead of only paying for a flat subscription. This model is common for APIs, AI products, credit-based systems, compute workloads, and other products where consumption happens during runtime.

Stripe is good at collecting payment, but it does not answer the runtime questions your product still has to solve: who currently has access, how much usage is left, and when units should be consumed after real work happens.

This guide explains how to implement usage-based billing with Stripe while using Licenzy to handle checkout, entitlement state, access checks, and usage consumption.

What Stripe does vs what your app still needs

Stripe and your runtime layer have different responsibilities:

  • Stripe handles payment collection and billing events.
  • Your app still needs usage tracking and runtime access control.
  • Your backend still needs a reliable way to decide when usage can be consumed.
  • Your product still needs entitlement-backed state that survives retries and billing changes.

Licenzy sits in that runtime gap. It creates checkout through Stripe, processes billing updates into entitlement state, answers access checks, and lets your backend atomically consume usage when work actually happens.

Example runtime flow

A common backend pattern is: verify access first, perform the protected action, then consume units with an idempotency key tied to the business event.

Access check and usage consumptionConcrete integration example
TypeScript
const accessResponse = await fetch("https://api.licenzy.app/v1/access/check", {
  method: "POST",
  headers: {
    Authorization: "Bearer lz_test_***",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    subject_ref: "user_123",
  }),
});

const access = await accessResponse.json();

if (!access.allowed) {
  throw new Error("Access denied.");
}

const usageResponse = await fetch("https://api.licenzy.app/v1/usage/consume", {
  method: "POST",
  headers: {
    Authorization: "Bearer lz_test_***",
    "Idempotency-Key": "usage-user_123-request_987",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    subject_ref: "user_123",
    units: 100,
  }),
});

const usage = await usageResponse.json();
// { ok: true, consumed: 100, usage_remaining: 900 }

This keeps runtime enforcement on the server, where usage decisions can be retried safely and tied to real completed work.

Common mistakes

  • Tracking usage only in the frontend instead of in a trusted backend flow.
  • Granting access from a payment success page before webhook processing finishes.
  • Consuming usage without an idempotency key tied to the actual business event.
  • Mixing payment state and runtime access state instead of reading entitlement-backed access.
  • Creating Stripe payment flows without a consistent subject reference across checkout, access, and usage.
Operational rule
Payment collection, access decisions, and usage consumption are related but separate concerns. Keep them connected through Licenzy runtime state instead of treating them as one browser-driven flow.