Billing & Usage

The metered-billing layer used by the SaaS deployment coalesces usage events in-memory, periodically flushes them to a BillingSink, verifies Stripe webhook signatures in constant time, and falls back to a JSONL log when Stripe is unreachable.

Metering pipeline

graph LR REQ["Request handler"] -->|UsageMeter.record| METER["UsageMeter<br/>(in-memory coalesce)"] METER -->|periodic drain| EVENTS["Vec&lt;UsageEvent&gt;"] EVENTS --> STRIPE["StripeBillingSink<br/>(feature: stripe)"] EVENTS --> JSONL["JsonlBillingSink<br/>(always available)"] EVENTS --> DB["billing_usage_events<br/>(Postgres)"] STRIPE -->|failure| JSONL WEBHOOK["Stripe webhook"] -->|HMAC-SHA256| VERIFY["verify_webhook_signature"] VERIFY --> SUB["billing_subscriptions<br/>row update"]

Usage taxonomy (UsageKind)

Kind Quantity unit Recorded by
events_ingestedcount of inbound rowsQUIC ingester after WAL append
monthly_active_usersdistinct user ids in periodlogin pipeline (idempotent)
storage_gbgigabytesnightly storage scanner
egress_gbgigabytesgateway response counter
device_connectionspeak concurrentSDK connection registry

Air-gap fallback

Stripe support is feature-gated (--features stripe); air-gapped builds compile without HTTP/TLS dependencies and simply rely on JsonlBillingSink. Each line is a single UsageEvent record, suitable for offline reconciliation:

JSON
{"id":"7c2f...","org_id":"ae5a...","kind":"events_ingested","quantity":18234,"recorded_at":"2026-04-26T14:21:00Z","billed_at":null}
{"id":"8a91...","org_id":"ae5a...","kind":"storage_gb","quantity":42,"recorded_at":"2026-04-26T14:21:00Z","billed_at":null}

When the Stripe sink fails (network error, 5xx, signature mismatch on response) the metered batch is automatically replayed into the JSONL sink so no usage is dropped.

REST surface

Method & path Purpose
GET /api/v1/admin/billing/subscriptionCurrent subscription tier, status, period end.
GET /api/v1/admin/billing/usagePer-kind usage for the active billing period.
GET /api/v1/admin/billing/invoicesInvoice history.
POST /admin/billing/portalIssue a Stripe Customer Portal one-time URL.
POST /admin/billing/webhookStripe webhook receiver. Verifies Stripe-Signature in constant time.

Schema

SQL
CREATE TABLE billing_subscriptions (
    id                       UUID PRIMARY KEY,
    org_id                   UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
    stripe_customer_id       TEXT,
    stripe_subscription_id   TEXT,
    plan_tier                TEXT NOT NULL,
    status                   TEXT NOT NULL,
    current_period_end       TIMESTAMPTZ,
    created_at               TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE billing_usage_events (
    id           UUID PRIMARY KEY,
    org_id       UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
    kind         TEXT NOT NULL,
    quantity     BIGINT NOT NULL,
    recorded_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    billed_at    TIMESTAMPTZ
);
CREATE INDEX billing_usage_events_org_kind_idx
    ON billing_usage_events(org_id, kind);

Related

Questions?

Reach out for help with integration, deployment, or custom domain codecs.