Channels & Integrations
Ten channel types feed one agent runtime. Inbound traffic arrives on
provider-specific webhook endpoints in core/omnichannel, gets
normalised into channel sessions, routed to an agent, and answered through
the matching outbound adapter. Contact pairing, operator handoff and
per-channel plan gates sit on top. Webhook paths verified against the
controllers; channel set from the ChannelType enum.
flowchart LR
subgraph ext["External platforms"]
WA["WhatsApp
(Twilio / Meta Cloud)"] & TG["Telegram"] & SL["Slack"] & DC["Discord"] & MS["Teams"] & META["Messenger /
Instagram / X"] & EM["Email (IMAP)"] & VO["Voice
(LiveKit / Twilio PSTN)"]
end
ext --> WH["/webhook/* controllers
signature verification"]
WH --> NORM["Normalise → channel session
contacts / pairing / routing"]
NORM --> SEL["AgentRelevanceSelector
(Jev: which agent replies)"]
SEL --> AGENT["AgentChatService
+ GuardedToolCallback pipeline"]
AGENT --> OUT["Outbound adapters
(per-channel credential rows)"]
AGENT -->|"request_human tool"| OP["Operator handoff inbox"]
1. Channel types
ChannelType: WHATSAPP, MESSENGER,
INSTAGRAM, SLACK, DISCORD, X,
TELEGRAM, EMAIL, TEAMS,
VOICE. WhatsApp has three distinct transports (Twilio, Meta Cloud
API, and the unofficial QR path) under one type.
2. Inbound webhook surface
| Path | Channel / provider | Notes |
|---|---|---|
POST /webhook/twilio/whatsapp | WhatsApp via Twilio | Form-encoded; Twilio signature validation |
POST /webhook/twilio/sms | SMS via Twilio | TwilioSmsWebhookController |
POST /webhook/twilio/voice[/collect|/menu-collect] | PSTN voice via Twilio | TwilioVoiceWebhookController — gather/collect turns |
POST /webhook/twilio (status) | Twilio delivery status | TwilioStatusCallbackController |
GET/POST /webhook/meta/whatsapp | WhatsApp via Meta Cloud API | GET = verification challenge, POST = messages |
GET/POST /webhook/meta/social | Messenger / Instagram | MetaSocialWebhookController |
POST /webhook/telegram/{botUserId} | Telegram | Per-bot webhook keyed on bot user id |
POST /webhook/discord | Discord | Inbound via a Node gateway service, which calls this endpoint |
POST /webhook/slack | Slack | Events API surface |
POST /webhook/teams/{appId} | Microsoft Teams | Per-app-id routing |
| — (polled) | EmailInboundPollingJob over IMAP — no webhook | |
| — (WebRTC/WS) | Voice | LiveKit browser call + Twilio voice relay WebSocket; see §4 |
3. Session, pairing and routing model
- Contacts, groups, routing: inbound senders resolve to contact records; group chats and per-channel routing rules decide which agent (or agents) may answer.
- Pairing inbox: unknown senders land in a pairing flow — pairing sessions are keyed on the external id, so a paired contact keeps memory continuity across conversations.
- Agent selection: on shared channels,
AgentRelevanceSelector(backed by the Jev decision model) picks which agent replies. - Operator handoff: the
request_humantool parks the conversation in the operator inbox for a human to take over. - Universal disconnect (2026-09-23): one disconnect path now cleanly tears down a channel linkage regardless of provider.
- Channel health endpoints: per-channel status is exposed for ops dashboards.
- Plan gate:
whatsapp_allowedinplan_limitscontrols WhatsApp access per plan.
4. Voice pipeline
Two independent voice paths exist and are deliberately not unified:
| Path | Transport | Pipeline |
|---|---|---|
In-app calling (core/voice) | LiveKit WebRTC, browser-to-agent, zero PSTN | LiveKit token issuance → push-to-talk STT → AgentChatService → TTS |
PSTN voice (omnichannel/twilio) | Twilio voice + relay WebSocket | voice-agent worker: Silero VAD → Whisper STT → core SSE turn → ElevenLabs TTS |
All TTS output passes through SpeechTextNormalizer
(core/speech) so the engine says something sensible — markdown,
emoji and raw URLs are rewritten before synthesis. ElevenLabs voice cloning is
BYOK per tenant (own credential table); Sarvam covers the Indic-language
STT/TTS surface.
5. Credential storage
Each channel connection stores provider credentials as encrypted rows in the
tenant schema (AES-256-GCM via EncryptionService) — e.g.
TwilioCredential, MetaWhatsAppCredential,
DiscordCredential, Google refresh tokens. Outbound adapters read
them at send time; webhook inbound paths authenticate via provider signature
validation rather than JWT.
6. Known risks and quirks
WhatsApp connects through Meta's WhatsApp Cloud API or a Twilio WhatsApp sender. Unofficial personal-number clients violate WhatsApp's terms of service and are not part of this platform.
| Area | Behaviour |
|---|---|
| Discord | Not a direct webhook from Discord — a Node gateway service maintains the socket and forwards into /webhook/discord |
| Polling job, not push — inbound latency is bounded by the IMAP poll interval | |
| Meta webhooks | GET challenge + POST messages on the same path; signature verification required before processing |
| Voice | Two stacks (LiveKit in-app, Twilio PSTN) — check which one a report refers to before debugging |