TL;DR: Lago is the open-source billing engine you self-host — event ingestion, flexible pricing models, and full invoice control with no per-transaction fees. Orb is the developer-first cloud billing platform — SQL-based metrics, real-time usage tracking, and first-class support for complex pricing tiers. Metronome handles billing for high-scale infrastructure companies — pre-aggregated metering, commits and drawdowns, and enterprise contract management. In 2026: Lago if you want open-source and full control, Orb if you want the best developer experience for usage-based pricing, Metronome if you're selling infrastructure with complex enterprise contracts.
Key Takeaways
- Lago: Open-source (AGPLv3), self-hosted or cloud. Event-based metering, percentage/graduated/package/volume pricing. Ruby/Rails backend, GraphQL + REST APIs. Best for teams wanting full billing control without vendor lock-in
- Orb: Cloud-only, developer-first. SQL-based metric definitions, real-time usage dashboards, automatic invoice generation. Best for SaaS with complex usage-based pricing tiers
- Metronome: Cloud-only, enterprise-focused. Pre-aggregated metering at scale, commits/credits/drawdowns, contract management. Best for infrastructure companies with high event volumes and enterprise sales motions
Lago — Open-Source Billing Engine
Lago gives you a self-hosted billing engine with event ingestion, flexible pricing, and invoice generation — all without per-transaction fees.
Ingesting Usage Events
// lago-node SDK — send usage events
import { Client } from "lago-javascript-client";
const lago = Client({
apiKey: process.env.LAGO_API_KEY!,
apiUrl: "https://billing.yourcompany.com", // self-hosted
});
// Ingest a usage event
await lago.events.createEvent({
event: {
transaction_id: crypto.randomUUID(), // idempotency key
external_subscription_id: "sub_12345",
code: "api_calls", // matches billable metric code
timestamp: Math.floor(Date.now() / 1000),
properties: {
tokens: 1500,
model: "gpt-4",
},
},
});
// Batch ingest for high throughput
await lago.events.createBatchEvents({
events: usageRecords.map((record) => ({
transaction_id: record.id,
external_subscription_id: record.subscriptionId,
code: "api_calls",
timestamp: record.timestamp,
properties: { tokens: record.tokens },
})),
});
Defining Billable Metrics
// Create a billable metric — sum of tokens used
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "API Token Usage",
code: "api_calls",
aggregation_type: "sum_agg",
field_name: "tokens",
recurring: false,
},
});
// Weighted sum — track compute hours
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "Compute Hours",
code: "compute",
aggregation_type: "weighted_sum_agg",
field_name: "cpu_seconds",
weighted_interval: "seconds",
},
});
// Unique count — active users per billing period
await lago.billableMetrics.createBillableMetric({
billable_metric: {
name: "Active Users",
code: "active_users",
aggregation_type: "unique_count_agg",
field_name: "user_id",
},
});
Pricing Plans with Graduated Tiers
// Create a plan with graduated pricing
await lago.plans.createPlan({
plan: {
name: "Growth Plan",
code: "growth",
interval: "monthly",
amount_cents: 4900, // $49 base price
amount_currency: "USD",
pay_in_advance: true,
charges: [
{
billable_metric_id: apiMetricId,
charge_model: "graduated",
properties: {},
graduated_ranges: [
{ from_value: 0, to_value: 10000, per_unit_amount: "0.00", flat_amount: "0" },
{ from_value: 10001, to_value: 100000, per_unit_amount: "0.002", flat_amount: "0" },
{ from_value: 100001, to_value: null, per_unit_amount: "0.001", flat_amount: "0" },
],
},
{
billable_metric_id: computeMetricId,
charge_model: "percentage",
properties: {
rate: "2.5",
fixed_amount: "0.10", // minimum charge per transaction
},
},
],
},
});
Subscriptions and Invoices
// Assign a subscription
await lago.subscriptions.createSubscription({
subscription: {
external_customer_id: "cust_42",
plan_code: "growth",
external_id: "sub_12345",
billing_time: "calendar", // or "anniversary"
},
});
// Retrieve current usage for a customer
const usage = await lago.customers.findCustomerCurrentUsage(
"cust_42",
{ external_subscription_id: "sub_12345" }
);
console.log(usage.data.charges_usage);
// [{ billable_metric: { code: "api_calls" }, units: "45230", amount_cents: 7046 }]
// List invoices
const invoices = await lago.invoices.findAllInvoices({
external_customer_id: "cust_42",
status: "finalized",
});
// Download invoice PDF
const pdf = await lago.invoices.downloadInvoice(invoices.data[0].lago_id);
Webhooks for Payment Integration
import express from "express";
import crypto from "crypto";
const app = express();
app.post("/webhooks/lago", express.raw({ type: "application/json" }), (req, res) => {
// Verify webhook signature
const signature = req.headers["x-lago-signature"];
const hmac = crypto.createHmac("sha256", process.env.LAGO_WEBHOOK_SECRET!);
hmac.update(req.body);
const expected = hmac.digest("hex");
if (signature !== expected) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body.toString());
switch (event.webhook_type) {
case "invoice.created":
// Trigger payment via Stripe/payment provider
processPayment(event.invoice);
break;
case "invoice.payment_status_updated":
// Update internal records
updatePaymentStatus(event.invoice);
break;
case "event.error":
// Handle ingestion errors
handleEventError(event);
break;
}
res.status(200).send("OK");
});
Orb — Developer-First Cloud Billing
Orb is a cloud billing platform with SQL-based metric definitions, real-time usage tracking, and automatic invoice generation.
Ingesting Events
import Orb from "orb-billing";
const orb = new Orb({ apiKey: process.env.ORB_API_KEY! });
// Ingest a single event
await orb.events.ingest({
events: [
{
idempotency_key: crypto.randomUUID(),
external_customer_id: "cust_42",
event_name: "api_call",
timestamp: new Date().toISOString(),
properties: {
tokens: 1500,
model: "gpt-4",
region: "us-east-1",
},
},
],
});
// High-throughput batch ingestion
const batch = usageRecords.map((r) => ({
idempotency_key: r.id,
external_customer_id: r.customerId,
event_name: "api_call",
timestamp: r.timestamp,
properties: { tokens: r.tokens, model: r.model },
}));
await orb.events.ingest({ events: batch });
// Amend or backfill events
await orb.events.update(eventId, {
event_name: "api_call",
timestamp: correctedTimestamp,
properties: { tokens: 1200 }, // corrected value
});
SQL-Based Metric Definitions
-- Orb lets you define metrics using SQL
-- Total token usage per billing period
SELECT SUM(properties.tokens) AS usage
FROM events
WHERE event_name = 'api_call'
-- Unique active users
SELECT COUNT(DISTINCT properties.user_id) AS usage
FROM events
WHERE event_name = 'api_call'
-- Weighted compute cost by model tier
SELECT SUM(
CASE
WHEN properties.model = 'gpt-4' THEN properties.tokens * 3
WHEN properties.model = 'gpt-3.5' THEN properties.tokens * 1
ELSE properties.tokens * 0.5
END
) AS usage
FROM events
WHERE event_name = 'api_call'
Creating Plans and Price Configurations
// Create a plan with tiered pricing
const plan = await orb.plans.create({
name: "Growth",
description: "Usage-based growth plan",
prices: [
{
name: "API Calls",
item_id: apiCallItemId,
cadence: "monthly",
model_type: "tiered",
tiered_config: {
tiers: [
{ first_unit: "0", last_unit: "10000", unit_amount: "0.000" },
{ first_unit: "10001", last_unit: "100000", unit_amount: "0.002" },
{ first_unit: "100001", last_unit: null, unit_amount: "0.001" },
],
},
},
{
name: "Platform Fee",
item_id: platformFeeItemId,
cadence: "monthly",
model_type: "unit",
unit_config: { unit_amount: "49.00" },
},
],
});
// Matrix pricing — different rates by dimension
const matrixPlan = await orb.plans.create({
name: "Compute Plan",
prices: [
{
name: "Compute",
item_id: computeItemId,
cadence: "monthly",
model_type: "matrix",
matrix_config: {
dimensions: ["region", "instance_type"],
matrix_values: [
{ dimension_values: ["us-east-1", "gpu"], unit_amount: "0.50" },
{ dimension_values: ["us-east-1", "cpu"], unit_amount: "0.05" },
{ dimension_values: ["eu-west-1", "gpu"], unit_amount: "0.60" },
{ dimension_values: ["eu-west-1", "cpu"], unit_amount: "0.06" },
],
},
},
],
});
Subscriptions and Real-Time Usage
// Create a subscription
const subscription = await orb.subscriptions.create({
customer_id: customerId,
plan_id: plan.id,
start_date: new Date().toISOString(),
});
// Fetch real-time usage for current billing period
const usage = await orb.subscriptions.fetchUsage(subscription.id, {
granularity: "day",
});
for (const metric of usage.data) {
console.log(`${metric.billable_metric.name}:`);
for (const point of metric.usage) {
console.log(` ${point.timeframe_start}: ${point.quantity} units`);
}
}
// Get cost estimate before invoice finalization
const costs = await orb.subscriptions.fetchCosts(subscription.id, {
timeframe_start: billingPeriodStart,
timeframe_end: new Date().toISOString(),
});
console.log(`Estimated cost: $${costs.data.reduce(
(sum, c) => sum + parseFloat(c.total), 0
).toFixed(2)}`);
Invoices and Credits
// List invoices
const invoices = await orb.invoices.list({
customer_id: customerId,
status: ["issued", "paid"],
});
// Issue credit grant
await orb.customers.credits.create(customerId, {
entry_type: "increment",
amount: 500, // $500 in credits
per_unit_cost_basis: "1.00",
expiry_date: "2026-12-31",
reason: "Annual prepayment discount",
});
// Check credit balance
const balance = await orb.customers.credits.list(customerId);
console.log(`Credit balance: $${balance.data[0].balance}`);
// Credits automatically apply to invoices
// No additional code needed — Orb deducts credits before charging
Webhooks
import Orb from "orb-billing";
// Verify webhook signature
function verifyOrbWebhook(req: Request, secret: string): boolean {
const signature = req.headers["x-orb-signature"];
const timestamp = req.headers["x-orb-timestamp"];
const body = req.body;
const payload = `${timestamp}.${body}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return signature === `v1=${expected}`;
}
app.post("/webhooks/orb", (req, res) => {
if (!verifyOrbWebhook(req, process.env.ORB_WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = req.body;
switch (event.type) {
case "invoice.issued":
chargeCustomer(event.invoice);
break;
case "invoice.payment_failed":
notifyCustomer(event.invoice);
break;
case "subscription.started":
provisionResources(event.subscription);
break;
}
res.status(200).send("OK");
});
Metronome — Enterprise Usage Billing at Scale
Metronome handles billing for high-scale infrastructure companies — pre-aggregated metering, commits and drawdowns, and enterprise contract management.
Ingesting Usage Events
// Metronome REST API — ingest usage
const response = await fetch("https://api.metronome.com/v1/ingest", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify([
{
customer_id: "cust_42",
event_type: "api_call",
timestamp: new Date().toISOString(),
transaction_id: crypto.randomUUID(),
properties: {
tokens: 1500,
model: "gpt-4",
region: "us-east-1",
},
},
]),
});
// Metronome supports millions of events per second
// Events are pre-aggregated for fast query performance
// Batch ingest via CloudWatch/Datadog/S3 integration
// Configure in Metronome dashboard — no code needed for infra metrics
Billable Metrics
// Create a billable metric
const metric = await fetch("https://api.metronome.com/v1/billable-metrics/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "API Token Usage",
aggregation_type: "sum",
aggregation_key: "tokens",
event_type_filter: {
in_values: ["api_call"],
},
group_keys: [["model"], ["region"]], // group by model and region
}),
});
// Composite metrics for complex pricing
const compositeMetric = await fetch(
"https://api.metronome.com/v1/billable-metrics/create",
{
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Weighted Compute Units",
aggregation_type: "sum",
aggregation_key: "compute_units",
event_type_filter: {
in_values: ["compute"],
},
// Pre-compute weighted values at ingestion time
}),
}
);
Contracts with Commits and Drawdowns
// Create a customer contract with prepaid commits
const contract = await fetch("https://api.metronome.com/v1/contracts/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: "cust_42",
starting_at: "2026-01-01T00:00:00Z",
rate_card_id: rateCardId,
commits: [
{
product_id: apiProductId,
type: "prepaid",
amount: 50000_00, // $50,000 prepaid commit
priority: 1,
applicable_product_ids: [apiProductId, computeProductId],
},
],
// Credits roll over if not used
// Overage charged at list price
}),
});
// Check commit balance
const balance = await fetch(
`https://api.metronome.com/v1/contracts/getBalance?customer_id=cust_42`,
{
headers: { Authorization: `Bearer ${METRONOME_API_KEY}` },
}
);
const data = await balance.json();
console.log(`Remaining commit: $${(data.balance / 100).toFixed(2)}`);
console.log(`Used: $${(data.consumed / 100).toFixed(2)}`);
Rate Cards and Pricing
// Create a rate card with tiered pricing
const rateCard = await fetch("https://api.metronome.com/v1/rate-cards/create", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Enterprise Rate Card",
rates: [
{
product_id: apiProductId,
entitled: true,
rate_type: "tiered",
tiers: [
{ size: 1000000, price: 0.002 }, // First 1M at $0.002
{ size: 10000000, price: 0.0015 }, // Next 10M at $0.0015
{ size: null, price: 0.001 }, // Beyond at $0.001
],
},
{
product_id: computeProductId,
entitled: true,
rate_type: "flat",
price: 0.05, // $0.05 per compute unit
},
],
}),
});
// Custom rate card overrides per customer
const override = await fetch("https://api.metronome.com/v1/rate-cards/override", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
rate_card_id: rateCard.id,
customer_id: "cust_42",
overrides: [
{
product_id: apiProductId,
rate_type: "tiered",
tiers: [
{ size: 1000000, price: 0.0015 }, // Negotiated discount
{ size: null, price: 0.0008 },
],
},
],
}),
});
Usage Queries and Invoices
// Query aggregated usage
const usage = await fetch("https://api.metronome.com/v1/usage", {
method: "POST",
headers: {
Authorization: `Bearer ${METRONOME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: "cust_42",
billable_metric_id: apiMetricId,
window_size: "day",
starting_on: "2026-03-01T00:00:00Z",
ending_before: "2026-03-09T00:00:00Z",
group_by: { key: "model" },
}),
});
const usageData = await usage.json();
for (const window of usageData.data) {
console.log(`${window.start}: ${window.value} tokens (${window.group.model})`);
}
// List invoices
const invoices = await fetch(
`https://api.metronome.com/v1/invoices?customer_id=cust_42&status=finalized`,
{
headers: { Authorization: `Bearer ${METRONOME_API_KEY}` },
}
);
Feature Comparison
| Feature | Lago | Orb | Metronome |
|---|---|---|---|
| Deployment | Self-hosted or cloud | Cloud only | Cloud only |
| License | AGPLv3 (open-source) | Proprietary | Proprietary |
| Metric Definition | UI/API config | SQL-based | API config |
| Pricing Models | Graduated, percentage, package, volume | Tiered, matrix, BPS, package | Tiered, flat, per-unit |
| Event Ingestion | REST + batch | REST + batch + amend | REST + batch + integrations |
| Real-Time Usage | API queries | Real-time dashboards + API | Pre-aggregated queries |
| Commits/Drawdowns | ❌ | Credits | Full commit management |
| Contract Management | Basic subscriptions | Subscriptions + credits | Enterprise contracts |
| Invoice Generation | Built-in + PDF | Automatic | Automatic |
| Webhook Events | HMAC-signed | HMAC-signed | HMAC-signed |
| Payment Integration | Stripe, GoCardless, Adyen | Stripe, payment agnostic | Stripe, payment agnostic |
| Multi-Currency | ✅ | ✅ | ✅ |
| Tax Integration | Basic | Anrok integration | Custom |
| API Style | REST + GraphQL | REST | REST |
| SDK Languages | Node.js, Python, Ruby, Go | Node.js, Python | REST-only (no SDK) |
| Ideal Scale | Startups to mid-market | Mid-market to enterprise | Enterprise / high-volume |
When to Use Each
Choose Lago if:
- You want open-source billing with no vendor lock-in
- You need self-hosted deployment for data residency/compliance
- You want to avoid per-transaction billing platform fees
- Your pricing models are standard (graduated, percentage, volume)
- You're comfortable managing infrastructure (Postgres, Redis, Rails)
Choose Orb if:
- You need complex usage-based pricing with SQL-level flexibility
- You want real-time usage dashboards out of the box
- Matrix pricing (price varies by multiple dimensions) is important
- You need event amendment/backfill capabilities
- Developer experience and API design matter most
Choose Metronome if:
- You process millions of usage events per second
- You sell with prepaid commits, drawdowns, and enterprise contracts
- Your sales team negotiates custom rate cards per customer
- You need integrations with CloudWatch, Datadog, or S3 for metering
- You're an infrastructure/platform company with enterprise sales motions
Revenue Recognition and Finance Team Integration
Usage-based billing creates a specific accounting challenge: revenue is recognized when the service is delivered, not when the invoice is paid. For software companies following ASC 606 or IFRS 15, your billing system needs to support deferred revenue tracking — showing revenue earned each day regardless of when invoices are issued. The degree to which each platform automates this for finance teams differs.
Lago exports invoice data in structured formats (JSON, CSV) that can feed an accounting system, but it doesn't model deferred revenue natively — that calculation lives in your ERP (QuickBooks, NetSuite). Lago is a billing engine, not an accounting system. Finance teams working with Lago typically build a reconciliation job that reads invoice and usage data from the Lago API and writes period-level revenue attribution into their accounting tool.
Orb has more explicit support for finance workflows. It exposes period-level revenue attribution and integrates with Anrok for tax calculations, which matters for SaaS companies with international customers subject to VAT or digital services taxes. The Orb dashboard includes revenue metrics broken down by billing period and pricing component, which reduces the manual work of producing finance reports from raw invoice data.
Metronome is designed for the most complex finance scenarios: enterprise contracts with prepaid commits and drawdowns. When a customer signs a $100K annual commit with a mix of prepaid credits and overage pricing, the accounting treatment of drawdowns versus overages is distinct — consumed prepaid credits reduce a deferred liability account, while overages are recognized as current-period revenue. Metronome's commit tracking maps directly to this accounting model. For infrastructure companies in Series B+ with dedicated finance teams that need billing data to flow into their ERP without manual reconciliation, Metronome's contract management reduces the monthly close workload.
Pricing Model Design: The Edge Cases That Matter
The billing system you choose constrains the pricing models you can offer customers. Understanding the limits before implementation avoids having to change billing systems six months after launch.
Lago handles graduated pricing, percentage-based pricing, and package pricing cleanly. Where Lago shows limits is in multi-dimensional pricing — pricing that varies by more than one attribute simultaneously. If you want to charge differently based on API calls where the price depends on both the customer tier and the geographic region of the request, Lago requires modeling this as separate billable metrics (one per tier-region combination). Orb solves this directly with matrix pricing: a single metric can have a price grid indexed by multiple dimensions (region × compute type × tier), and Orb evaluates the correct cell automatically. For product-led growth companies where pricing complexity is a competitive differentiator, Orb's matrix pricing handles cases that Lago cannot without workarounds.
Metronome's rate cards offer per-customer pricing flexibility that neither Lago nor Orb matches at the same granularity. Enterprise sales teams often negotiate custom pricing per customer — different tier thresholds, different overage rates, different credit terms. Metronome's rate card override system allows this without creating separate plan configurations per customer, which becomes critical when you have 50+ enterprise customers each with slightly different contract terms. The alternative in Lago or Orb is managing per-customer plan variants, which grows exponentially with customer count and becomes a maintenance burden at scale.
Operational Reliability: Idempotency and Event Throughput
Billing systems have a fundamental correctness requirement: every usage event must be counted exactly once. Double-billing a customer or missing billable usage both create problems — one triggers refund requests and customer service incidents, the other destroys revenue. The robustness of each platform's event ingestion pipeline matters as much as its pricing model flexibility.
All three platforms use idempotency keys to prevent duplicate event processing. Send the same event twice with the same key and only one will be recorded. This works well for controlled ingestion from application code where you generate the key at event creation time and retain it for retries.
Where the platforms differ is in handling high-volume event streams. Metronome is designed for pre-aggregated ingestion at millions of events per second. It integrates directly with CloudWatch and Datadog, so infrastructure metrics become billable events without going through application code at all — your AWS usage data flows into Metronome's billing engine via native integration. For infrastructure companies where billable events are generated by cloud systems rather than application logic, this matters significantly. Lago and Orb both accept events via REST API with batch endpoints; their throughput is sufficient for most SaaS products but not for raw infrastructure telemetry at cloud-provider scale. If your application generates more than 100K events per minute, evaluate Metronome's ingestion architecture before committing to Lago or Orb.
Methodology
Feature comparison based on Lago v1.x (self-hosted), Orb, and Metronome public API documentation as of March 2026. Pricing models based on published documentation. Code examples use official SDKs where available (Lago Node.js, Orb Node.js) and direct REST API calls for Metronome. Evaluated on: deployment flexibility, pricing model support, event ingestion, real-time usage access, contract management, and developer experience.
See also: Polar vs Paddle vs Gumroad 2026, Stripe Billing vs Chargebee vs Recurly 2026, and Best Payment Libraries for Node.js 2026