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.
Recommended flow
A practical implementation usually follows this order:
- Configure Stripe and create Licenzy products in the matching mode.
- Create checkout through
POST /v1/checkout/session. - Let Stripe billing webhooks update entitlement state inside Licenzy.
- Check access from your backend before protected work starts.
- Consume usage only when the work is accepted or completed.
That sequence keeps payment, access, and usage connected without forcing your app to reconstruct billing state manually.
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.
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.