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<UsageEvent>"]
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_ingested | count of inbound rows | QUIC ingester after WAL append |
| monthly_active_users | distinct user ids in period | login pipeline (idempotent) |
| storage_gb | gigabytes | nightly storage scanner |
| egress_gb | gigabytes | gateway response counter |
| device_connections | peak concurrent | SDK 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/subscription | Current subscription tier, status, period end. |
| GET /api/v1/admin/billing/usage | Per-kind usage for the active billing period. |
| GET /api/v1/admin/billing/invoices | Invoice history. |
| POST /admin/billing/portal | Issue a Stripe Customer Portal one-time URL. |
| POST /admin/billing/webhook | Stripe 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
- Control plane —
plan_tieron the Org drives feature flags. - Air-gap install — how the JSONL fallback is consumed.
Questions?
Reach out for help with integration, deployment, or custom domain codecs.