Skip to content

Billing

Cowliss billing is a prepaid wallet, not a subscription. There are no plans, tiers, or feature entitlements: money is the only gate, and every feature (journeys, custom sending domains, segments) is available to every org from the first event.

  • Free allowance: every org gets $1 per month, resetting at each UTC calendar month. Unused allowance does not roll over.
  • Wallet: spend past the allowance draws a prepaid balance, funded by fixed-amount one-time payments ($10 / $50 / $200) through Stripe Checkout. Wallet credit carries over month to month; it is never debited per use and never expires.
  • Remaining credit = max(0, allowance + balance − spend). At zero, the platform hard-stops: ingestion answers 429 over_quota with Retry-After, and email/webhook sends record skipped_quota in the delivery log. Nothing is ever billed in arrears: there is no overage invoice, because nothing can run past zero.
  • Warning: from 80% of credit used, the dashboard shows a top-up banner, so a rejection never surprises you.

Usage is one weighted spend across four metered actions, computed in integer micro-dollars (1,000,000 per dollar: no float ever touches money):

ActionRate
Event ingested$2 per 10,000
Email sent$4 per 10,000
Webhook delivered$2 per 10,000
Journey run$2 per 10,000

What counts, and what does not:

  • Events are customer-written rows, including batch backfill. System events Cowliss writes itself (system.segment_entered, system.segment_exited, system.consent_revoked, delivery feedback) are never metered.
  • Sends count when settled; failed, skipped (skipped_*), and dry-run (would_*) deliveries are excluded by construction.
  • Journey runs count real instances; dry runs cost nothing.
  • A price of 0 would make an action free without removing it from the meter: the catalog is one map in packages/shared, tweakable in one place.

Spend is counted, never incremented: every number is a query over rows that already exist (ClickHouse events, the delivery log, journey_runs), so the meter can never drift from the thing it bills for, and a replayed webhook or a re-run backfill cannot double-charge.

POST /v1/billing/topups { amountMicros: 10_000_000 } (a preset, not free-form)
→ 201 { checkoutUrl } (Stripe-hosted Checkout)
… pay on Stripe …
Stripe webhook checkout.session.completed → wallet credited, ledger row completed

The wallet is credited by the webhook and nowhere else: the topup endpoint answers only a checkout URL, and the ledger row it writes stays pending until Stripe confirms. The wallet_topups ledger is the audit trail of every top-up and its state.

  • Dashboard: Settings → Billing shows balance, this month’s spend against the allowance, remaining credit, and top-up history.
  • API: GET /v1/billing returns the allowance, wallet balance, period bounds, spend, remaining credit with warn/over flags, the usage counts, and the price catalog that weighted them.
  • CLI: cow billing prints the same summary.

One creditMicros function in packages/shared computes remaining credit for the API’s 429 on ingestion, the worker’s skipped_quota send gate, the billing page, and the dashboard banner: four surfaces, one arithmetic. A runaway journey hits the same wall as a runaway import: no bug can burn unbounded volume or credit.