Channel Provisioning Protocol β
Domain: Communications (Shared Core Domain) Trigger: Operator-initiated channel registration (post-provisioning) Output: Configured
channel_accountswith active CPaaS provider connections Sources: communications.md, ADR-003L3 DoD: β Schema | β API | β Edge States β Ready to Code
Β§1 Overview β
Channel provisioning is an explicit post-provisioning operator action β ProvisionTenant (ADR-003) seeds notification_templates but does NOT create channel_accounts. Channel registration requires operator action (Meta Business Verification, SES domain verification) that the system cannot automate at signup.
The Workspace UI shows a "Complete your communications setup" onboarding checklist. Seeded notification_templates reference channels that do not yet exist β the notification pipeline defers template activation until the matching ChannelAccount reaches ACTIVE.
Β§2 ChannelAccount Lifecycle State Machine β
stateDiagram-v2
[*] --> PENDING_VERIFICATION : registerChannel
PENDING_VERIFICATION --> ACTIVE : Provider verification completes
PENDING_VERIFICATION --> SUSPENDED : Verification failed / timed out (30d)
ACTIVE --> SUSPENDED : Manager action or provider revocation
ACTIVE --> REVOKED : Provider permanently bans account
SUSPENDED --> ACTIVE : Re-verification or manager reactivation
REVOKED --> [*] : Terminal β must register new account| From | To | Trigger | Side Effects |
|---|---|---|---|
| β | PENDING_VERIFICATION | registerChannel Hasura Action | Creates channel_accounts + operator_integrations (PENDING). Initiates provider verification. |
PENDING_VERIFICATION | ACTIVE | Provider verification completes | operator_integrations.status β CONNECTED. Pipeline can dispatch. change_event (CONFIG). |
PENDING_VERIFICATION | SUSPENDED | Verification failed/timed out | operator_integrations.status β FAILED + error_message. |
ACTIVE | SUSPENDED | Manager action or provider revocation detected | operator_integrations.status β DISCONNECTED. Pipeline skips channel β fallback chain. |
SUSPENDED | ACTIVE | Re-verification or manager reactivates | operator_integrations.status β CONNECTED. |
ACTIVE | REVOKED | Provider permanently bans account | Terminal. operator_integrations.status β DISCONNECTED. Must register new account. |
Suspension detection:
- WhatsApp:
channel_health_checkHasura Cron Trigger (daily, 03:00 UTC) polls Meta WABA status. - Email: SES
accountSendingPausedSNS notification β auto-suspend.
Β§3 WhatsApp Registration Flow (Meta Embedded Signup) β
Phase 1 uses Meta's Embedded Signup β a JavaScript SDK handling Facebook Login, business verification, and WABA creation in an iframe.
sequenceDiagram
participant UI as Workspace UI
participant Meta as Meta Embedded Signup
participant API as NestJS registerChannel
participant DB as PostgreSQL
UI->>Meta: FB.login() (iframe)
Meta-->>UI: waba_id, phone_number_id, short_lived_token
UI->>API: registerChannel(WHATSAPP, credentials)
API->>Meta: Exchange short-lived β long-lived token
API->>Meta: POST /{waba_id}/subscribed_apps (per-WABA webhook override)
Meta->>API: GET /webhooks/meta/{tenant_id}?hub.verify_token=...
API-->>Meta: hub.challenge (200 OK)
API->>DB: INSERT channel_accounts (PENDING_VERIFICATION)
API->>DB: INSERT operator_integrations (PENDING)
API-->>UI: { channel_account_id, status }Steps:
- Operator opens Workspace Settings β Communications β Add Channel β WhatsApp.
- Meta Embedded Signup launches (
FB.login()withwhatsapp_business_managementpermissions). - Operator completes flow β Meta returns
waba_id,phone_number_id, short-lived token. - Frontend calls
registerChannelwith the credentials. - Handler: exchange token β generate
webhook_verify_tokenβ subscribe to Meta webhooks viaPOST /<WABA_ID>/subscribed_apps(WABA-level override, not app-level/{app_id}/subscriptions) β encrypt secrets β INSERT both tables β poll Meta Business Verification status.
NOTE
Busflow Meta App: All tenants share one Busflow-owned Meta App (BSP model). app_id and app_secret are platform-level constants stored in provider_config for worker convenience.
WARNING
Blocking prerequisite: The Busflow Meta App must pass Meta App Review for whatsapp_business_management and whatsapp_business_messaging before any tenant can provision WhatsApp.
Β§4 Email Registration Flow (SES Domain Verification) β
Operator adds DNS records (DKIM, SPF/DMARC) to prove domain ownership.
Steps:
- Operator opens Add Channel β Email, enters domain (e.g.,
reisen-mueller.de). - Frontend calls
registerChannelwith domain and sender email. - Handler: create SES domain identity β enable DKIM β create Configuration Set β create SNS topics + subscription β return DNS records.
- Operator adds DNS records at domain registrar.
ses_verification_checkCron Trigger (every 15 min) polls SES verification status.- On success β
ACTIVE. On 30-day timeout βSUSPENDED.
DNS Record Display UI: Panel with copy-to-clipboard buttons, per-record status indicators (β³/β /β), and "Verify Now" button.
NOTE
SES Sandbox: Busflow's AWS account must promote out of SES Sandbox before any tenant sends production emails.
Β§5 SMS Registration (Platform-Managed) β
Phase 1: Platform-managed, not operator-provisioned. DACH Sender ID registration requires per-country carrier pre-registration β unsuitable for self-service.
- Busflow registers a platform-wide Sender ID ("Busflow") with DACH carriers via SNS.
- All tenants share this Sender ID.
registerChannelfor SMS creates the row withstatus = ACTIVEimmediately.provider_configstores platform-level SNS config.
Phase 2: operators can register their own Sender ID (1β3 week carrier approval).
Β§6 Hasura Action Contracts β
registerChannel β
| Property | Type | Required | Description |
|---|---|---|---|
| Input | |||
channel_type | String | β | WHATSAPP, EMAIL, SMS |
display_name | String | β | Operator-editable label |
sender_identity | String | β | Canonical sender identifier |
whatsapp_config | Object | β | Required if WHATSAPP (Β§3) |
email_config | Object | β | Required if EMAIL (Β§4) |
| Output | |||
channel_account_id | UUID | β | Created ChannelAccount ID |
status | String | β | PENDING_VERIFICATION or ACTIVE |
dns_records | Object[] | β | EMAIL only: DNS records to add |
| Errors | |||
CHANNEL_ALREADY_EXISTS | Tenant has existing account for this channel type | ||
PROVIDER_ERROR | Meta/SES API call failed | ||
VALIDATION_ERROR | Invalid input |
Authorization: MANAGER role only.
updateChannelConfig β
Updates operator-editable fields. Does NOT allow changing channel_type or secrets (re-registration required).
| Property | Type | Required | Description |
|---|---|---|---|
channel_account_id | UUID | β | Target account |
display_name | String | β | Updated label |
sender_identity | String | β | Updated sender (re-verification for Email) |
reply_to_email | String | β | Email only |
Authorization: MANAGER role only.
testChannel β
Sends a test message to verify end-to-end connectivity.
| Property | Type | Required | Description |
|---|---|---|---|
channel_account_id | UUID | β | Channel to test |
test_recipient | String | β | Phone (E.164) or email |
Authorization: MANAGER or DISPATCHER.
suspendChannel / reactivateChannel β
| Property | Type | Required | Description |
|---|---|---|---|
channel_account_id | UUID | β | Target account |
reason | String | β | Logged in change_events |
Side effects (suspend): Pipeline skips channel. Queued messages β FAILED with failed_reason = 'channel_suspended'. Fallback chain activates.
Authorization: MANAGER role only.
Β§7 Meta Webhook Setup & Verification β
NOTE
WABA-level override: This section covers webhook setup via POST /<WABA_ID>/subscribed_apps. The per-tenant callback URL (/api/webhooks/meta/{tenant_id}) receives both inbound messages and delivery status callbacks. See notification-pipeline-protocol.md Β§8 for the delivery webhook handler spec.
During registration (automatic):
- Handler calls
POST /<WABA_ID>/subscribed_appswith tenant-specificoverride_callback_urlandverify_token. This is a WABA-level override β it does NOT overwrite the app-level subscription. Each tenant's WABA independently routes to its own callback URL. - Meta sends
GET /api/webhooks/meta/{tenant_id}?hub.mode=subscribe&hub.verify_token={token}&hub.challenge={challenge}. - Controller: extract
tenant_idβ loadchannel_accountsβ comparewebhook_verify_tokenβ respond withhub.challengeor HTTP 403.
Runtime signature verification (every delivery):
- Meta sends
POST /api/webhooks/meta/{tenant_id}withX-Hub-Signature-256. - Handler computes
HMAC-SHA256(app_secret, body)β compare. - Match β process. Mismatch β HTTP 401, log security alert.
IMPORTANT
Shared Meta App Secret: All tenants share one app_secret. Tenant isolation relies on WABA-level scoping (each WABA receives webhooks for its own phone number only).
Β§8 operator_integrations β channel_accounts Sync Contract β
Two tables, two schemas, one business concept. Writes happen atomically within a single transaction.
| Event | channel_accounts | operator_integrations |
|---|---|---|
| Registered | INSERT PENDING_VERIFICATION | INSERT PENDING |
| Verification succeeds | β ACTIVE | β CONNECTED |
| Verification fails | β SUSPENDED | β FAILED + error_message |
| Manager suspends | β SUSPENDED | β DISCONNECTED |
| Manager reactivates | β ACTIVE | β CONNECTED |
| Provider revokes | β REVOKED | β DISCONNECTED |
| Manager deletes | DELETE | β DISCONNECTED |
NOTE
Cross-schema write justification: Controlled exception to "contexts communicate via events." Channel provisioning is an administrative configuration action (infrequent, operator-initiated) that must be atomic. Async sync would create consistency gaps in the Workspace UI.
Β§9 operator_integrations β CPaaS Types β
integration_type | Description | Provisioned By |
|---|---|---|
META_WHATSAPP | WhatsApp via Meta Cloud API | registerChannel (operator) |
AWS_SES | Email via Amazon SES | registerChannel (operator) |
AWS_SNS | SMS via Amazon SNS | registerChannel (platform-managed Phase 1) |
Config shapes live in schema-backoffice.md Β§operator_integrations.