August 2026 · 7 min read
Adding Usage-Based Billing to AI Features
Usage-based billing for AI features is a database problem before it is a payments problem. How I keep metering idempotent, protect margin, and leave pricing changeable.

Usage-based billing relies on explicit application events
Usage-based billing is an application-level orchestration problem, not a passive invoice-tracking task. It works when the billing logic lives in explicit database state transitions rather than inside the AI call itself. The provider cannot tell you what was consumed in the terms you need, because it does not know your customer hierarchy or your business rules. It sees a stream of raw requests and nothing more.[1]
I treat every billable AI action as an explicit transaction. On the AI chat backend I built, eligibility was never inferred from the provider API; I kept a local record of account permissions and usage entitlement instead. When a user sends a prompt, the system checks my own database rather than the model provider. That keeps billing deterministic and under my control, and if the provider goes offline the billing logic keeps working, because it was never coupled to the inference call in the first place.[5]
Decoupling AI orchestration from the billing state machine
A billing system should never depend on a live HTTP response from an AI provider. Connections drop and providers stall, and you still need to know exactly what was consumed rather than guessing at it. I separate AI orchestration from core product state, with a layer between prompt and response that owns the lifecycle of an AI job: success, failure, retry, and the billing that follows.[1]
I also moved away from streaming LLM responses over server-sent events. Those connections are fragile, and a drop mid-stream leaves an ambiguity you cannot resolve afterwards: did the user get the answer, and did they consume the tokens? If the only thing your system knows about is the stream, there is no way to tell. I replaced it with stateful polling, where the frontend checks a status endpoint for a completed job. Because the job is queued, the result, the error state and the token usage are written to a database row before the user ever sees the output. That row becomes the single point of truth for both the product experience and the billing engine.[1]
The auditability is the real payoff. When a customer questions an invoice I can point to the exact job ID behind a charge instead of parsing provider logs, and billing stays just another part of the application state machine rather than a separate system that has to be reconciled against one.[5]
Emitting events as durable records
Treat billing events as immutable audit logs. The event is written to your own database before it is ever reported to Stripe, and that local record is the source of truth. When the sync to the payment gateway fails on a network error, the persisted record is what makes a retry safe. I do not treat an API call as successful until there is a database record of both the intent and the outcome.[1][2]
Wrap the AI job processing and the billing event creation in one database transaction, and never fire an event at a billing API mid-process without a write confirming it happened. That is what makes the flow idempotent: if a job runs twice, you check the local billing table before calling the provider again. Double-charging a customer is the quickest way to lose their trust, and it is entirely preventable.[1]
The schema should mirror the lifecycle of a billable event: created in a pending state, moved to processed once the sync is acknowledged. That gives you something to reconcile against. A scheduled job looking for pending records older than a few minutes can retry them or flag them for a human, which is usually where billing bugs get caught before the invoice goes out.[6]
Protecting margins with cost floors and credit reservations
Runaway AI costs are not hypothetical. Waiting until the end of a billing cycle to discover that someone spent heavily on expensive model calls is too late, so I build in circuit breakers and credit reservations instead. This is a product quality concern as much as a financial one: when a user triggers an expensive loop, they should be stopped before they run up a bill they never meant to incur.[1][4]
A token-bucket limiter at the application layer is the piece I reach for. Before an AI job starts, the account is checked against a credit balance, and a request whose projected cost exceeds that balance is denied outright. It is a simple check, but it also stops an agent loop that would otherwise run indefinitely the moment the cost crosses the threshold. I arrived at this one the hard way, after launch traffic exposed provider limits we had underestimated.[1]
Hard spending caps on the provider account are worth setting too, even when the internal logic is sound. I treat provider-side limits as the last line of defence rather than a substitute: if the application code has a bug, the cap is what stops a bad day from becoming a catastrophic one.[1]
Implementation steps for Laravel-based backends
In Laravel, Cashier gives you the hooks for reporting usage. You define a meter in the Stripe dashboard, then push usage events from your background jobs, with a schema that maps jobs to meters. I keep the meter ID in configuration rather than in the job code, so switching pricing models does not mean touching the jobs themselves.[2][6]
You can report usage within your job class after successful completion. Keep the Stripe ID of the meter stored in your config or environment variables so the billing code remains decoupled from the specific pricing model.$user->reportUsage('ai_tokens_used', $tokenCount, $timestamp);The job should keep running even when the usage report fails. I wrap usage reporting in a try-catch, log the failure and mark the job as needing reconciliation, so the user still gets their result and a background worker can settle the billing later. A failed usage report should never cost someone the answer they were waiting on.[2]
Why I avoid direct provider-to-invoice syncing
Using a model provider's usage dashboard as the primary billing source is a mistake I would not repeat. Those dashboards lack the context to know which of your users triggered which request, so the attribution layer has to be yours. Record events locally and you keep the ability to attribute usage to the right account, and billing stays consistent when the provider changes its reporting metrics or delays an update.[1][5]
Centralising billing state has saved hours of manual reconciliation. When someone asks about a charge, there is a local row with the timestamp, model version, token count and job ID, which turns billing from a mysterious monthly number into a report you can actually explain.[1]
Next steps for your billing pipeline
Before deploying any of this, I check five things: a dedicated table for usage events, every model call wrapped in a queue, an idempotency key on every billable event, a credit check before each request, and a console command that reconciles local usage against the payment provider. The reconciliation command is the one people skip, and it is the one that catches the bugs.[5][6]
Start with one small, low-stakes AI feature and watch how the usage patterns actually show up in your database, because they rarely match the model you priced against. Once the flow holds up, extend it to heavier models or high-volume agent work. Billing is a foundation rather than an afterthought, and retrofitting it onto a product that already has usage is far more painful than building it alongside the first metered feature.[5]
Sources
- Usage-Based Billing for AI Companies | Stripe
- Laravel Cashier (Stripe) | Laravel 13.x - The clean stack for Artisans and agents
- Usage-based billing software for AI | Metronome, a ...
- Managing AI experiences enabled by usage-based billing | Microsoft Learn
- How to implement usage-based billing for AI services
- The best billing solutions for AI products and services | Lago
Related articles
How I Think About Stripe Payment Flows in Product Backends
A practical blueprint for payment flows: checkout state, idempotent webhooks, subscription access, grace periods, and operational debugging.
Adding AI Features Without Turning the Product Into a Demo
How to structure AI product features around service boundaries, queues, rate limits, state, and failure handling so they survive real users.