CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-05-04Intermediate

Implementing Usage-Based Billing with Claude API + Stripe: A Minimal Setup for Indie Developers

Learn how to measure Claude API token consumption and implement usage-based billing with Stripe Meter Events. A minimal Node.js setup that indie developers can ship in a weekend, with real gotchas from production.

claude-api81stripe6usage-based-billingnodejs2api-monetization

The moment you decide to monetize a product built on Claude API, billing design becomes the first real obstacle. Flat-rate pricing sounds simple, but API costs don't stay flat — a single user who sends lengthy document analysis requests can cost you more than your entire monthly plan revenue.

I've built Claude-powered services with Stripe metered billing, and the biggest lesson was: don't try to build the perfect billing system on day one. Ship a working minimal implementation, see how real users behave, then optimize. This guide gives you that minimal implementation — a complete Node.js setup that records Claude token consumption to Stripe Meter Events, with the production gotchas I learned the hard way.

Flat Rate vs. Usage-Based — The Real Trade-offs

Flat-rate pricing has one undeniable advantage: it's easy to explain. "$9.99/month" converts well on landing pages.

The problem with flat-rate for Claude API products is variance. A user asking short questions might consume 200 tokens per request. A user uploading 20-page PDFs for summarization might consume 8,000 tokens per request — 40x more. If you designed your $9.99 plan assuming the former and attract the latter, you lose money on every active user.

Usage-based billing solves this structurally. You charge proportional to consumption, so your margin holds regardless of usage patterns.

There's another benefit I didn't anticipate: you accumulate real usage data before committing to a pricing model. Launch with metered billing, watch how your actual users behave for a month, then design an informed flat-rate or tiered plan based on real percentiles. You can't do that if you start with flat-rate.

For background on Claude API pricing and how to think about cost structure, see Claude API Pricing Guide 2026.

How Stripe Meter Events Work

Stripe's recommended approach for usage-based billing is now Meter Events — the previous Usage Records API has been deprecated for new implementations.

The model has three parts:

  • Meter: Defines what you're measuring (e.g., "total tokens consumed"), including how values aggregate over a billing period
  • Meter Event: An API call you make to record actual usage as it happens
  • Subscription Price: A price object linked to the Meter, which Stripe uses to calculate the amount to charge at period end

Every time a user calls your Claude-powered feature, you record the token count to Stripe. At the end of the billing period, Stripe sums everything up and charges the customer's card automatically.

Getting Token Count from Claude API

Claude's response object includes token usage data out of the box. No extra API calls needed.

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
 
const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello" }],
});
 
// Token usage is always present in the response
const inputTokens = response.usage.input_tokens;
const outputTokens = response.usage.output_tokens;
const totalTokens = inputTokens + outputTokens;
 
console.log(
  `Tokens: ${inputTokens} input + ${outputTokens} output = ${totalTokens} total`
);
// Example: Tokens: 12 input + 38 output = 50 total

If you're using prompt caching, cache_read_input_tokens will also appear in response.usage. For a minimal implementation, tracking input_tokens + output_tokens is sufficient. See Claude API Token Counting and Cost Optimization for more advanced tracking.

The Minimal Implementation — Complete Node.js Code

Here's the full working implementation. One function handles both the Claude API call and the Stripe billing event.

// claude-billing.js
import Anthropic from "@anthropic-ai/sdk";
import Stripe from "stripe";
 
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
 
// Must match the event_name you set when creating the Meter in Stripe
const METER_EVENT_NAME = "claude_tokens";
 
/**
 * Call Claude API and record token consumption to Stripe.
 * @param {string} stripeCustomerId - The customer's Stripe ID (e.g., "cus_XXXXXXXXXX")
 * @param {string} userMessage - The user's message to send to Claude
 */
async function callClaudeWithBilling(stripeCustomerId, userMessage) {
  // 1. Call the Claude API
  const response = await anthropic.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    messages: [{ role: "user", content: userMessage }],
  });
 
  const totalTokens =
    response.usage.input_tokens + response.usage.output_tokens;
 
  // 2. Record usage to Stripe Meter Events
  try {
    await stripe.v2.billing.meterEvents.create({
      event_name: METER_EVENT_NAME,
      payload: {
        value: String(totalTokens), // Stripe requires a string value
        stripe_customer_id: stripeCustomerId,
      },
    });
    console.log(
      `✅ Billing recorded: ${totalTokens} tokens for customer ${stripeCustomerId}`
    );
  } catch (billingError) {
    // Always log billing failures — silent failures mean lost revenue
    console.error(`⚠️ Billing record failed: ${billingError.message}`, {
      customerId: stripeCustomerId,
      tokens: totalTokens,
      timestamp: new Date().toISOString(),
    });
    // TODO: Add to retry queue
  }
 
  return {
    content: response.content[0].text,
    tokensUsed: totalTokens,
  };
}
 
// Example usage
const result = await callClaudeWithBilling(
  "cus_XXXXXXXXXX", // Replace with actual Stripe customer ID
  "Explain Next.js performance optimization strategies"
);
 
console.log("Response:", result.content);
console.log("Tokens used:", result.tokensUsed);

Expected behavior: Claude responds to the user message, and a claude_tokens event appears in your Stripe dashboard. At month end, Stripe automatically sums all events and generates an invoice.

Setting Up Stripe (One-Time Configuration)

Before the code can run, configure three things in Stripe.

Step 1: Create a Meter

Stripe Dashboard → Billing → Meters → "+ Create meter"

  • Event name: claude_tokens (must match METER_EVENT_NAME in your code)
  • Default aggregation: sum (total tokens consumed per billing period)

After creation, save the Meter ID — you'll need it when creating the price.

Step 2: Create a Usage-Based Price

Products → "+ Add product" → Select "Usage-based" as the pricing model

  • Meter: select your claude_tokens meter
  • Per unit: set your price per 1,000 tokens

A reasonable starting point: claude-sonnet-4-6 costs $3/1M input tokens and $15/1M output tokens. Set your per-unit price to cover your cost plus margin, then adjust once you see actual usage data.

Step 3: Create Subscriptions on User Signup

// Call this when a new user registers
const subscription = await stripe.subscriptions.create({
  customer: stripeCustomerId,
  items: [{ price: process.env.STRIPE_USAGE_PRICE_ID }],
});

With these three steps configured, the billing function above will correctly record and attribute usage.

Three Production Gotchas

These are the issues I ran into after going live — not obvious from the documentation.

Gotcha 1: Meter Event failures silently drop revenue

The Claude API call succeeds, but the Stripe event send fails due to a network hiccup. The user gets their response, you incur the API cost, but nothing gets billed. This is the most dangerous failure mode because there's no immediate symptom.

The try/catch in the code above at least logs the failure. In production, you want more than a log — failed events should go into a database or queue for retry. A simple approach: save { customerId, tokens, timestamp, retried: false } to a database table on failure, and have a scheduled job retry anything with retried: false.

Gotcha 2: stripe.v2 requires a recent SDK version

Meter Events live under stripe.v2.billing.meterEvents.create() — not stripe.billing or stripe.usageRecords. Older versions of the Stripe SDK don't have the v2 namespace at all and will throw a confusing error. Run npm install stripe@latest before starting, and verify your Stripe SDK version is ≥ 12.0.

Gotcha 3: Design customer ID storage before you ship

The stripeCustomerId parameter works cleanly in this example, but in a real app you need a way to fetch it for the current user. The standard pattern: add a stripe_customer_id column to your users table, populate it during signup, and read it from the session when handling API requests. Retrofitting this after launch means migrating existing billing records — better to design it upfront.

Finishing Touches Before You Ship — Caps and Webhooks

Once the minimal version works, two things are worth adding before production: a cap that prevents overages, and a webhook that catches the close of each billing cycle.

Usage-based pricing leaves users with one nagging worry — they can't predict the bill. A monthly cap softens that and lowers the barrier to adoption. When I added metered billing to a personal project — one of the indie apps I build and run myself under Dolice — a handful of users burned through several times my expected volume in the first few days. Bolting a cap on afterward meant rewiring the request path, so I'd build the limit check at the same time you wire up billing.

// Middleware that records monthly usage in your DB and checks the cap
export async function checkAndTrackUsage(
  userId: string,
  estimatedCredits: number
): Promise<{ allowed: boolean; currentUsage: number; limit: number }> {
  const plan = await getUserPlan(userId);
  const currentUsage = await getMonthlyUsage(userId);
  const limit = plan.monthlyCredits; // e.g. 1000 credits on the Lite plan
 
  if (currentUsage + estimatedCredits > limit) {
    return { allowed: false, currentUsage, limit };
  }
 
  await incrementMonthlyUsage(userId, estimatedCredits);
  return { allowed: true, currentUsage: currentUsage + estimatedCredits, limit };
}
 
// Using it in an API endpoint
export async function POST(request: Request) {
  const { prompt, userId } = await request.json();
 
  const usageCheck = await checkAndTrackUsage(userId, 10); // ~10 credits estimated
  if (!usageCheck.allowed) {
    return Response.json(
      {
        error: "Monthly usage limit reached. Please upgrade your plan.",
        currentUsage: usageCheck.currentUsage,
        limit: usageCheck.limit,
      },
      { status: 429 }
    );
  }
 
  const subscriptionItemId = await getSubscriptionItemId(userId);
  const result = await callClaudeWithMetering(prompt, userId, subscriptionItemId);
  return Response.json({ result });
}

When the cap is hit, return a 429 and point users toward an upgrade. Including the current usage and limit in the error body helps them see exactly where they stand.

At the close of each billing cycle, listen for Stripe webhooks to reset the monthly counter. Handle two events and the basics are covered: reset the counter to zero on invoice.payment_succeeded, and suspend the service on invoice.payment_failed.

// app/api/stripe-webhook/route.ts
export async function POST(request: Request) {
  const sig = request.headers.get("stripe-signature")!;
  const body = await request.text();
 
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch {
    return new Response("Webhook signature verification failed", { status: 400 });
  }
 
  switch (event.type) {
    case "invoice.payment_succeeded": {
      const invoice = event.data.object as Stripe.Invoice;
      await resetMonthlyUsage(invoice.customer as string); // reset the monthly counter
      break;
    }
    case "invoice.payment_failed": {
      const invoice = event.data.object as Stripe.Invoice;
      await suspendUserService(invoice.customer as string); // pause on payment failure
      break;
    }
  }
 
  return new Response("ok");
}

Skipping signature verification (constructEvent) would let anyone POST fake events to your endpoint. Always verify with STRIPE_WEBHOOK_SECRET.

One last note on timing. If you use Claude's streaming API, token counts aren't final until the whole response completes. Reporting mid-stream means under-reporting whenever a connection drops. The safe approach is to report once, after the response finishes. If you want a live usage display, count locally in your database for the UI and keep the Stripe report asynchronous and post-completion.

Test with Stripe's Test Mode First

Stripe test mode uses sk_test_... keys and generates no real charges. Set your environment variables to test credentials and send a few real requests to verify the integration.

In your Stripe Dashboard, navigate to Billing → Usage → your Meter. You should see events appearing with the correct token counts and customer IDs. Once this looks right, switching to production is just swapping sk_test_... for sk_live_....

For a more complete implementation that adds retry logic, usage caps, and plan tier switching, see Monetizing a Personal SaaS with Claude API.

The best billing system is the one that ships. This minimal version handles the core loop correctly — Claude usage gets measured and recorded to Stripe. Ship it, watch how your first users actually behave, and then build the sophistication your real usage patterns actually require.

Required Environment Variables

Before running the code, you'll need four environment variables:

# .env
ANTHROPIC_API_KEY=sk-ant-...          # Your Anthropic API key
STRIPE_SECRET_KEY=sk_test_...         # Stripe secret key (use sk_live_ for production)
STRIPE_USAGE_PRICE_ID=price_...       # Price ID from Step 2 of Stripe setup
STRIPE_METER_ID=mtr_...               # Optional: Meter ID for reference

Keep STRIPE_SECRET_KEY server-side only — never expose it in client-side code or commit it to version control. Use a secrets manager or environment variable injection in your deployment pipeline.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-06-24
When Stripe's Bill and Your Own Ledger Drift Apart: Field Notes on Metered Billing for Claude API
Usage-based billing for Claude API looks clean until month-end, when Stripe's total and your own usage ledger quietly disagree. Here are the field-tested patterns for idempotent meter events, reconciliation jobs, and pricing-change-proof credits.
API & SDK2026-04-28
Building a Recurring Billing SaaS with Claude API and Stripe — From Architecture to Production
A complete architecture guide for building a SaaS product powered by Claude API with Stripe recurring billing. Covers usage metering, tiered pricing, webhook handling, and production deployment patterns.
API & SDK2026-07-13
Precision Lives in the Second Stage: Reranking a Personal Knowledge Search with Claude
Embedding search alone leaves 'semantically close but not the answer' passages at the top. This is a two-stage design that gathers candidates broadly, then lets Claude reorder them by answerability, with structured scoring, abstention, and a cost estimate.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →