Engagement Hub Billing
Dual-axis model
Each Hub accounts row has two independent settings:
| Axis | Values | Meaning |
|---|---|---|
| Payment plan | free, payg, subscription | How the tenant pays / whether usage is gated |
| Accounting mode | credit, allowance | What gets consumed when usage happens |
Legacy billing_mode is kept for backward compatibility and synced from the two axes:
| payment_plan | accounting_mode | legacy billing_mode |
|---|---|---|
| free | * | free |
| payg | * | payg |
| subscription | * | allowance |
Migration 1.60.0 adds payment_plan, accounting_mode, and unit-quota columns. Old billing_mode=allowance (credit cap) maps to subscription + credit; allowance_limit / allowance_used remain the subscription credit cap.
Payment plan behavior
| Payment plan | Credit mode | Allowance mode |
|---|---|---|
| Free | No deduction; usage logged | Same |
| Pay-as-you-go | Deduct credits_balance (+ grace); top-up via PATCH /accounts/{id}/credits | Deduct 1 unit from quota pools; top-up via PATCH /accounts/{id}/allowance-quotas |
| Subscription | Periodic credit cap reset (allowance_limit / allowance_used) + top-up balance | Periodic quota reset (limits fixed; *_used zeroed each period) |
Period boundaries use the account office timezone (domain_name on the account). Reset is lazy on first charge or billing-plan read after period end.
Credit accounting (rate-based)
Billable rate keys in service_pricing by engagement type (outbound SMS, phone, and WhatsApp share the same rate for a given type). Inbound email is always free.
| Key | Unit |
|---|---|
informational | Per engagement (any channel) |
conversational | Per engagement (any channel) |
appointment_booking | Per engagement (any channel) |
confirmational | Per engagement (any channel) |
outbound_email | Per email sent |
inbound_phone | Per inbound call |
chatbot | Per bot response |
Platform defaults live in platform_billing_defaults. Engagement rates API applies only when accounting_mode=credit.
Allowance accounting (unit quotas)
Four integer pools (1 unit per event):
| Pool | Consumed by |
|---|---|
| engagement | SMS, phone, WhatsApp outbound |
| outbound_email | Each outbound email |
| inbound_call | Each inbound phone engagement |
| chatbot | Each bot response |
| (inbound email) | Always free |
Engagement billing flow
- One
ensure_request_chargeperbilling_request_idatPOST /start - Classifier picks free / credit rate / quota pool from payment plan + accounting mode + call type + channel
- 402 responses indicate exhausted credits or a specific quota pool
Email
- Outbound:
EMAIL_OUT_{message_id}per send - Inbound pull: logged but not charged (
inbound_emailis free)
Chatbot
- domain-chatbot calls Hub
POST /billing/chargewithservice_type=chatbot
API (super-admin writes)
| Endpoint | Purpose |
|---|---|
GET/PUT /accounts/{id}/billing-plan | Payment plan, accounting mode, credit cap, quota limits |
PATCH /accounts/{id}/credits | Credit top-up (payg / subscription + credit) |
PATCH /accounts/{id}/allowance-quotas | Unit pool top-up (payg / subscription + allowance) |
GET/PUT /pricing/account/{id}/engagement-rates | Tenant engagement-type rates (credit mode only) |
GET/PUT /pricing/platform-defaults | Default rates for new accounts |
POST /billing/charge | Idempotent per-unit charge |
Billing plan response shape
{
"payment_plan": "payg",
"accounting_mode": "allowance",
"subscription_period": "monthly",
"credit": {
"balance": 0,
"grace": 10,
"subscription_limit": 0,
"subscription_used": 0,
"subscription_remaining": 0
},
"allowance_quotas": {
"engagement": { "limit": 100, "used": 5, "remaining": 95 },
"outbound_email": { "limit": 50, "used": 0, "remaining": 50 },
"inbound_call": { "limit": 20, "used": 0, "remaining": 20 },
"chatbot": { "limit": 200, "used": 10, "remaining": 190 }
}
}
Bootstrap env vars (rate seeds only)
COST_INFORMATIONAL_CALL=5.0
COST_CONVERSATIONAL_CALL=15.0
COST_APPOINTMENT_BOOKING_CALL=20.0
COST_CONFIRMATIONAL_CALL=10.0
COST_EMAIL=1.0
COST_INBOUND_PHONE=0.0
COST_CHATBOT=0.5
Migrations
- 1.57.0 — billing columns,
platform_billing_defaults, canonical rates - 1.59.0 — backfill
billing_mode=free(schema_validator default conflict) - 1.60.0 — dual-axis columns, backfill from legacy
billing_mode - 1.61.0 — credit rates by engagement type (
informational,conversational, etc.) instead of channel keys
On upgrade, existing accounts default to free + credit unless previously on payg or legacy allowance (→ subscription + credit). Restart Engagement Hub once to apply pending migrations.
Super-admin UI: Domains Overview → Manage → Pricing (/en/domains/{domain}/pricing).