Skip to main content

Engagement Hub Billing

Dual-axis model

Each Hub accounts row has two independent settings:

AxisValuesMeaning
Payment planfree, payg, subscriptionHow the tenant pays / whether usage is gated
Accounting modecredit, allowanceWhat gets consumed when usage happens

Legacy billing_mode is kept for backward compatibility and synced from the two axes:

payment_planaccounting_modelegacy 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 planCredit modeAllowance mode
FreeNo deduction; usage loggedSame
Pay-as-you-goDeduct credits_balance (+ grace); top-up via PATCH /accounts/{id}/creditsDeduct 1 unit from quota pools; top-up via PATCH /accounts/{id}/allowance-quotas
SubscriptionPeriodic credit cap reset (allowance_limit / allowance_used) + top-up balancePeriodic 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.

KeyUnit
informationalPer engagement (any channel)
conversationalPer engagement (any channel)
appointment_bookingPer engagement (any channel)
confirmationalPer engagement (any channel)
outbound_emailPer email sent
inbound_phonePer inbound call
chatbotPer 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):

PoolConsumed by
engagementSMS, phone, WhatsApp outbound
outbound_emailEach outbound email
inbound_callEach inbound phone engagement
chatbotEach bot response
(inbound email)Always free

Engagement billing flow

  • One ensure_request_charge per billing_request_id at POST /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_email is free)

Chatbot

  • domain-chatbot calls Hub POST /billing/charge with service_type=chatbot

API (super-admin writes)

EndpointPurpose
GET/PUT /accounts/{id}/billing-planPayment plan, accounting mode, credit cap, quota limits
PATCH /accounts/{id}/creditsCredit top-up (payg / subscription + credit)
PATCH /accounts/{id}/allowance-quotasUnit pool top-up (payg / subscription + allowance)
GET/PUT /pricing/account/{id}/engagement-ratesTenant engagement-type rates (credit mode only)
GET/PUT /pricing/platform-defaultsDefault rates for new accounts
POST /billing/chargeIdempotent 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).