Channels & Omnichannel
The omnichannel layer (core/omnichannel) lets a tenant's
agents converse with end-users over ten messaging and voice channel
types. All channels share one inbound pipeline — pairing, session
resolution, agent routing, reply delivery — so adding a channel is an
adapter, not a new subsystem (ChannelType javadoc documents
this "add a case" design).
(InboundMessageDedupService)"] DD --> SESS["Session resolve + pairing gate
(ChannelSessionService)"] SESS --> ROUTE["Default-agent routing
(ChannelDefaultAgent)"] ROUTE --> AGT["Agent chat turn"] AGT --> OUT["Channel reply (adapter send)"] AGT -.handoff.-> OP["Operator inbox
(human take-over)"]
FR-CH-01 · Channel type catalogue
| ChannelType | Transport / onboarding | Notes |
|---|---|---|
WHATSAPP |
Two official onboarding paths: Meta Cloud API (including Embedded Signup) and the Twilio path (TwilioWhatsAppChannelAdapter). |
The flagship channel; subject to the whatsapp_allowed plan gate. |
MESSENGER |
Meta webhook + page credentials. | First channel added after WhatsApp; proved the channel-agnostic pipeline. |
INSTAGRAM |
Meta webhook + credentials. | Same machinery as Messenger. |
SLACK |
Slack app credentials; events webhook. | Workspace-scoped sessions. |
DISCORD |
Via the dedicated gateway service (Discord requires a persistent gateway connection, so it lives outside core/app). |
See Service Topology. |
X |
X/Twitter API credentials. | Mentions/DM ingress. |
TELEGRAM |
Bot token; webhook (TelegramWebhookController) + TelegramApiClient sends. |
Supports workflow triggers and agent events on the same webhook. |
EMAIL |
IMAP inbound polling (EmailInboundPollingJob) + SMTP outbound. |
Tenant supplies mailbox credentials. |
TEAMS |
Tenant's own Microsoft Bot Framework (Azure Bot) registration; JWT-signed activities at /webhook/teams/{appId} (migration V240). |
Replies via Bot Framework connector; per-conversation service URLs stored in teams_conversation_route. |
VOICE |
Two paths: LiveKit browser calls (in-app voice) and Twilio voice relay (TwilioVoiceWebhookController + VoiceRelayWebSocketHandler). |
Voice replies are spoken live over the WebSocket bridge — they bypass the async approval queue by design (a live call cannot wait mid-conversation). external_id is the caller's number so memory persists call to call. |
FR-CH-02 · Contacts, groups & routing
Inbound identities resolve to contacts; contacts can be grouped and routed
to different default agents per channel or per account
(ChannelDefaultAgent). A channel session
(ChannelSession) keys on external_id — the
sender's channel-native identity — so a returning WhatsApp sender or
voice caller resumes their conversation and memory.
FR-CH-03 · Pairing inbox
First-time senders land in the pending-pairings inbox
(ChannelPairingController) — a pairing gate that holds a new
contact until a tenant member approves the pairing. This is the
anti-abuse boundary between "a stranger messaged the number" and "an
agent consumes LLM budget replying to them".
FR-CH-04 · Operator human-handoff inbox
OperatorInboxService provides human handoff: a conversation
can be flagged to a human operator, who replies through the same channel
adapter while the agent stands down. Handoff works across all channel
types uniformly.
FR-CH-05 · Universal disconnect
Any channel connection can be severed from one disconnect surface — credentials are revoked, webhooks/pollers stand down, and sessions on that account stop routing. One operation regardless of adapter.
FR-CH-06 · Health checks & credentials
| Requirement | Description | Status |
|---|---|---|
| FR-CH-06.1 | Per-channel health checks report connection state (ChannelStatusController) — credentials valid, webhook live, poller running. | live |
| FR-CH-06.2 | Credentials are per-channel and per-account, stored encrypted (see Secrets Management); each channel family has its own credential service (e.g. MetaWhatsAppCredentialService, TelegramCredentialService). | live |
| FR-CH-06.3 | Inbound deduplication (InboundMessageDedupService) drops retried/duplicated webhook deliveries so one user message never produces two agent turns. | live |
FR-CH-07 · Plan gates
WhatsApp connectivity is plan-gated by the whatsapp_allowed
flag in control_plane.plan_limits (FREE access was tuned by
migration V225__free_plan_whatsapp.sql — check current
rows for exact tier behaviour). Other channels are not individually
gated. Plan gates apply only when billing is on (see Functional Overview).
Cross-references
- Architecture: Channels & Integrations — adapter topology and the Discord gateway service.
- API: Webhooks — inbound webhook contracts and signature handling.
- VD-FN-006 — channel-send approvals and moderation on outbound replies.
- VD-FN-002 — channel trigger nodes and channel output.