Skip to content

Billing ​

Raven uses Hyperswitch as its payment gateway. Hyperswitch is an open-source unified payment-orchestration API that routes each charge to a downstream processor — Razorpay for UPI, RuPay, and domestic Indian cards; international card networks for everything else.

We did not pick Stripe. Stripe does not onboard merchants in India for our use case, which is the home market for the Raven-hosted offering. Hyperswitch gives us a single API surface while letting the routing layer pick the right local rails per transaction.

This page is about how the Raven-hosted SaaS bills its customers. Customers also pay LLM providers directly — that part of the bill is not routed through Raven. See LLM Providers for the BYOK model.

What gets billed ​

Raven only bills you for the platform itself. LLM token costs, embedding costs, and any provider-level charges are between you and Anthropic / OpenAI / Cohere / your chosen provider.

ChargeFrequencyNotes
Subscription plan (Free / Pro / Enterprise)MonthlyFlat fee per organisation. Defined in model.DefaultPlans() (internal/model/billing.go).
Seats (users above plan limit)MonthlyEnforced by quota check — see MaxUsers on each plan. Today over-limit users are blocked, not auto-billed.
Knowledge bases above plan limitMonthlySame shape as seats. MaxKBs is enforced; over-limit creation is blocked.
Storage above plan limitMonthlyMaxStorageMB; enforced, not metered overage.
Voice minutes above plan limitMonthlyMaxVoiceMinutesMonthly; enforced, not metered overage.
LLM tokensn/aNot billed by Raven. BYOK, paid to provider.

The codebase is honest about its current shape: the plan structure exists, the quotas are enforced, but metered overage billing is not yet wired. Going over a limit returns 402 Payment Required from /billing/usage and related endpoints. Upgrading the plan is the supported path; per-unit overage invoices are a future addition.

Hyperswitch architecture ​

Raven's API server talks to Hyperswitch over HTTP. Hyperswitch then decides which downstream processor handles the actual money movement.

┌──────────────┐   POST /payments   ┌──────────────┐   ┌───────────────┐
│  Raven API   │ ─────────────────▶ │ Hyperswitch  │ ─▶│  Razorpay     │
│  (Go, Gin)   │                    │ orchestrator │   │  (UPI/card)   │
│              │ ◀───── webhook ─── │              │ ─▶│  Card networks│
└──────────────┘  HMAC-SHA256 sig   └──────────────┘   └───────────────┘

The HTTP client lives at internal/hyperswitch/client.go. It is a thin wrapper around POST /payments, POST /payments/{id}/cancel, and GET /payments/{id}. The package doc-comment is the canonical description:

Package hyperswitch provides an HTTP client for the Hyperswitch payment orchestration API. Hyperswitch acts as the routing layer and connects to downstream payment processors (e.g. Razorpay for UPI/card in India).

Three environment variables drive the integration. They are bound in internal/config/config.go and consumed by the billing service:

Env varMaps toPurpose
RAVEN_HYPERSWITCH_BASE_URLhyperswitch.base_urlHyperswitch instance URL. Defaults to http://localhost:8090.
RAVEN_HYPERSWITCH_API_KEYhyperswitch.api_keyMerchant API key. Empty in dev defaults.
RAVEN_HYPERSWITCH_WEBHOOK_SECREThyperswitch.webhook_secretHMAC-SHA256 secret for inbound webhook verification.
RAVEN_HYPERSWITCH_RAZORPAY_KEY_IDhyperswitch.razorpay_key_idRazorpay merchant key (used when Razorpay is the routed processor).

If RAVEN_HYPERSWITCH_WEBHOOK_SECRET is empty, the webhook handler rejects every event with the error webhook signature verification is not configured. That is by design — silently accepting unsigned events would let anyone with the URL activate subscriptions.

See Configuration for the full env-var reference.

Plans and tiers ​

Three plans ship in the codebase today. Prices are in cents and live in model.DefaultPlans():

PlanIDMonthlyUsersWorkspacesKBsStorageVoice mins/mo
Freeplan_free$0.00523500 MB60
Proplan_pro$29.0025105010 GB1 200
Enterpriseplan_enterprise$99.00unlimitedunlimitedunlimitedunlimitedunlimited

Plans are returned by GET /api/v1/billing/plans. Frontend types live in frontend/src/api/billing.ts (PlanName = 'free' | 'pro' | 'enterprise').

Currency is reported in USD in code but Hyperswitch + Razorpay routing is expected to convert / display INR for Indian merchants — the payment_intents table defaults to INR (see migrations/00033_payment_intents.sql). The canonical currency for a billed transaction is whatever the PaymentIntent row stores.

Subscription lifecycle ​

A subscription is one row in the subscriptions table (migrations/00019_subscriptions.sql). The valid statuses are defined as a CHECK constraint in the migration and as Go constants in model.SubscriptionStatus:

trialing ─┐
          ├─▶ active ─┬─▶ past_due ──▶ canceled
          │           │
          │           └─▶ paused ──▶ active
          │
          └─▶ canceled
                          expired
StatusSet byMeaning
trialingService (future)Trial period; access granted, no charge.
activeInitial insert / webhook payment_succeededPlan is paid and current.
past_dueWebhook payment_failedRenewal payment failed; access policy is per-handler.
canceledDELETE /billing/subscriptions/:id or webhook subscription_cancelledTenant or processor cancelled.
pausedReservedDefined in schema; not currently emitted by the service.
expiredReservedDefined in schema; not currently emitted by the service.

A partial unique index enforces one active-ish subscription per org:

sql
CREATE UNIQUE INDEX idx_subscriptions_org_active
    ON subscriptions (org_id)
    WHERE status IN ('active', 'trialing', 'past_due');

Honest gap: the service today only emits active (on create), and transitions to canceled or past_due driven by webhooks. trialing, paused, and expired are schema-ready but not yet wired into a service path.

Payment methods ​

Whatever Hyperswitch routes to. The two production routes wired today:

  • UPI, RuPay, and domestic Indian cards via the Razorpay connector. RAVEN_HYPERSWITCH_RAZORPAY_KEY_ID is the merchant key.
  • International cards via the default Hyperswitch connector chain. The exact routing rules live in the Hyperswitch dashboard, not in Raven.

The frontend opens the Hyperswitch SDK / Razorpay checkout using the client_secret returned from POST /api/v1/billing/payment-intents. The secret is a transient field on the Subscription and PaymentIntent models (json:"client_secret,omitempty"); it is never persisted.

Webhooks ​

Hyperswitch posts payment events to POST /api/v1/billing/webhook. This endpoint does not use JWT auth — it verifies an X-Webhook-Signature header (HMAC-SHA256 of the raw body, hex-encoded, keyed by RAVEN_HYPERSWITCH_WEBHOOK_SECRET).

Implementation: internal/handler/billing.go ((h *BillingHandler).Webhook) delegates to internal/service/billing.go (HandleWebhook). The handler caps body size at 1 MB.

The payload shape is intentionally generic:

go
type HyperswitchWebhookPayload struct {
    EventType string         `json:"event_type"`
    Content   map[string]any `json:"content"`
}

The service handles three event types and ignores the rest with an info log:

event_typeAction
payment_succeededInsert into payment_events (idempotent by (payment_id, event_type)), extend current_period_end on the matching subscription.
payment_failedInsert into payment_events, leave subscription state alone (move to past_due is handled elsewhere by caller policy).
subscription_cancelledInsert into payment_events, mark subscription canceled.

payment_events exists for idempotency: replaying any event is a no-op. A unique index on (payment_id, event_type) blocks double-application. See migrations/00032_billing_rls_and_payment_events.sql.

Both subscriptions and payment_events have row-level security enabled. The webhook handler bypasses RLS for one transaction by calling SELECT set_config('app.bypass_rls', 'true', true), because the caller has no org context. Org attribution comes from content.metadata.org_id in the event payload, which Raven puts there when it creates the payment.

Tax and invoicing ​

Honest about the MVP:

  • GST / VAT computation: Not done by Raven. Hyperswitch / Razorpay are expected to handle this at the processor level for Indian transactions.
  • Invoice generation: Not implemented. There is no invoices table and no PDF rendering path. Customers see line-items in the Hyperswitch / Razorpay receipt email.
  • Dunning (retry-on-failure for past_due): Not implemented in Raven; whatever Hyperswitch does at the connector layer is what you get.
  • Proration on plan change: Not implemented. Plan changes apply at the next period boundary; the migration doesn't track mid-period changes.

These are tracked gaps. If you need a tax-compliant invoice today, pull it from the Hyperswitch dashboard, not the Raven UI.

Test mode ​

Raven ships a local Hyperswitch stack under deploy/hyperswitch/ for development. It is not part of the main docker-compose.yml — you bring it up separately:

bash
docker compose -f deploy/hyperswitch/docker-compose.hyperswitch.yml up -d

That brings up:

  • The Hyperswitch application server (port 8090 by default — matches the RAVEN_HYPERSWITCH_BASE_URL default).
  • Its backing PostgreSQL.
  • Its Redis instance.
  • The Hyperswitch web client (control center) for managing connectors and API keys.

Default admin API key: test_admin. Merchant API keys are minted through the dashboard or API once the stack is up.

For sandbox card / UPI numbers, use the test credentials documented at opensource.hyperswitch.io — the test data is upstream, not duplicated in this repo.

To test webhook delivery: configure the webhook destination in the Hyperswitch dashboard to point at your Raven API's /api/v1/billing/webhook. Use the dashboard's "send test event" feature once RAVEN_HYPERSWITCH_WEBHOOK_SECRET matches on both sides.

Self-host vs Raven-hosted ​

This page is about the Raven-hosted SaaS. If you are running Raven on your own infrastructure — Docker Compose, an edge node, a Raspberry Pi — you generally do not need Hyperswitch at all:

  • There is no SaaS subscription to charge for; the software is yours.
  • The subscriptions table will simply not be populated, and your org defaults to the implicit Free-tier subscription returned by model.DefaultFreeSubscription().
  • Plan limits still apply in code, but you can either change the defaults or run on Enterprise quotas (all limits -1).

Self-hosters can safely skip the rest of this guide. If you are building your own multi-tenant offering on top of Raven and need billing of your own, you can either point Raven at your own Hyperswitch tenant or wire a different gateway behind the same BillingService interface — the hyperswitch package is the only place the gateway choice leaks.

See Self-Hosting → Docker Compose for the operator path that skips billing entirely.

  • Configuration reference — full env-var list including the RAVEN_HYPERSWITCH_* variables.
  • Workspaces & Tenancy — how plans, quotas, and org isolation tie together.
  • LLM Providers — BYOK model for the Anthropic / OpenAI / Cohere bill that Raven does not collect.
  • Webhooks & Events — Raven's own outbound webhook system (separate from the inbound Hyperswitch webhook discussed here).