noburn
blog

How to Charge SaaS Customers for LLM Usage: A Technical Implementation Guide

Per-user LLM metering requires tracking spend by customer, enforcing limits, and connecting it to Stripe. Here is a complete implementation walkthrough with the three architectural approaches teams use.

by updated 3 min read#llm-billing #per-user-metering #saas

If you're charging SaaS customers for the LLM usage your product incurs on their behalf, you have three architectural options, and picking the wrong one for your traffic pattern means either a Stripe bill you're paying out of margin or a support queue full of billing disputes.

Approach 1: Real-time metered billing

Every LLM call gets logged with its actual cost (tokens in, tokens out, cost in USD, customer ID) at the moment it completes. Once per day or per hour, aggregate usage and update Stripe:

async function syncUsageToStripe(customerId) { const usage = await db.query( `SELECT SUM(cost_usd) as total FROM llm_events WHERE customer_id = $1 AND synced_to_stripe = false`, [customerId] ); await stripe.billing.meterEvents.create({ event_name: "llm_usage", payload: { stripe_customer_id: customer.stripeId, value: Math.round(usage.total * 100), // Stripe meter events are integer units }, }); await db.query( `UPDATE llm_events SET synced_to_stripe = true WHERE customer_id = $1`, [customerId] );}

This is accurate — the customer pays exactly what they cost — but it means you're extending credit. The LLM provider bills you immediately; Stripe invoices your customer on a monthly cycle. A customer who churns mid-cycle or disputes the charge leaves you holding the API cost.

Approach 2: Prepaid credits (wallet balance)

The customer buys a credit balance up front — $50 of usage, for example — and each LLM call debits the balance in real time. This flips the credit risk: you're never out of pocket, because the call gets blocked once the balance hits zero.

async function chargeAndCall(customerId, estimatedCost, makeCall) { const balance = await redis.get(`wallet:${customerId}`); if (parseFloat(balance) < estimatedCost) { throw new InsufficientBalanceError(); } const response = await makeCall(); const actualCost = computeActualCost(response); await redis.incrbyfloat(`wallet:${customerId}`, -actualCost); return response;}

The tradeoff is friction: customers have to think about topping up a balance, which is an unfamiliar mental model for SaaS buyers used to a flat monthly invoice. It works best for usage-heavy, high-variance products (AI agents, document processing) where a flat tier would either overcharge light users or undercharge heavy ones.

Approach 3: Fixed tiers with overage

The customer pays a flat monthly fee that includes a usage allowance (e.g., $29/month includes 10,000 requests), and usage beyond the allowance bills at a per-unit overage rate, synced to Stripe the same way as Approach 1. This is the most familiar model for SaaS buyers and the easiest to market, but it requires you to set the allowance high enough to cover typical usage without eating your margin on the included tier — which means you need real usage data before you can price it correctly, not just a guess.

Which approach to use

Prepaid credits (Approach 2) is the safest default if you don't yet have usage data: it caps your downside to zero regardless of how a customer behaves. Fixed tiers (Approach 3) converts well once you have enough usage history to price the allowance correctly. Real-time metered billing (Approach 1) is the right model for enterprise customers with negotiated contracts and net-30 terms, where extending credit is already part of the relationship.

Whichever model you choose, the same enforcement problem sits underneath all three: you need to know the cost of a call, per customer, before you decide whether to allow it — not after the bill arrives.

Where noburn fits

The tools discussed here handle observability, routing or evaluation, and all of them act once the LLM call has been made. noburn acts before it: one check() in your own code, placed before the provider call, returns allow or block against a project, per-user or per-run cap, so an over-budget call is never sent. It is not a proxy: your OpenAI or Anthropic client and its connection stay as they are. How the caps resolve, and the reason each one returns, is on the homepage.

Per-user caps and usage quotas keep one customer from spending another's budget, and Pro adds passthrough billing through Stripe. The Free plan covers 10,000 recorded calls a month with no card (current plans: pricing). Setup takes one install and one check: the quickstart, then per-user budgets and agent runs in the SDK reference.