| name | telephony-messaging-adapters |
| description | Use when adding a new telephony or messaging provider, implementing webhook validation, working with IVR flows, or modifying call routing. Also use when the user mentions "telephony adapter", "messaging adapter", "webhook validation", "TwiML", "NCCO", "Plivo XML", "Asterisk", "SMS", "WhatsApp", "Signal", "provider", or needs to understand the multi-provider abstraction layer. |
Telephony & Messaging Adapters
Architecture
TelephonyAdapter interface (apps/worker/telephony/adapter.ts): 17 methods covering IVR, call control, webhook parsing, validation, and recordings.
MessagingAdapter interface (apps/worker/messaging/adapter.ts): 6 methods for inbound/outbound messaging + webhook validation.
All methods return provider-agnostic types. Business logic never touches provider formats.
Existing Providers
| Provider | Format | Lines | Extends | Notes |
|---|
| Twilio | TwiML XML | 492 | — | Base implementation, most features |
| SignalWire | TwiML XML | 60 | TwilioAdapter | Override base URL + webhook header |
| Vonage | NCCO JSON | 495 | — | JWT auth, different action names |
| Plivo | Plivo XML | 449 | — | Similar to TwiML, different elements |
| Telnyx | TwiML XML | ~400 | TwilioAdapter | Override base URL + API key auth |
| Bandwidth | BXML XML | ~350 | — | Similar to TwiML, BXML format |
| Asterisk | ARI JSON | 543 | SipBridgeAdapter | Self-hosted, ARI backend |
| FreeSWITCH | ESL JSON | ~400 | SipBridgeAdapter | Self-hosted, ESL backend |
| sip-bridge | Protocol-agnostic | — | — | PBX_TYPE selects ARI/ESL/Kamailio |
Adding a New Voice Provider - Checklist
- Create
apps/worker/telephony/provider.ts implementing TelephonyAdapter
- Add
'provider' to TelephonyProviderType union in packages/shared/types.ts
- Add to
TELEPHONY_PROVIDER_LABELS map (display name)
- Add config fields to
TelephonyProviderConfig interface
- Add to
PROVIDER_REQUIRED_FIELDS map (credential field names)
- Add
case 'provider': in createAdapterFromConfig() in apps/worker/lib/do-access.ts
- Add cases in
webrtc-tokens.ts and sip-tokens.ts
- UI auto-discovers from type maps — NO UI changes needed
Webhook Validation Reference
| Provider | Algorithm | Header | Data String | Constant-Time? |
|---|
| Twilio | HMAC-SHA1 | X-Twilio-Signature | url + sorted form params concat | Yes (XOR) |
| SignalWire | HMAC-SHA1 | X-SignalWire-Signature or X-Twilio-Signature | same as Twilio | Yes |
| Vonage Voice | JWT | Authorization: Bearer | JWT claims verification | N/A |
| Vonage SMS | HMAC-SHA256 | X-Vonage-Signature | raw request body (hex) | Yes (XOR) |
| Plivo | MD5 | X-Plivo-Signature | form body (base64) | NO — needs fix |
| Asterisk | HMAC-SHA256 | X-Asterisk-Signature | callId+timestamp+nonce (hex) | Yes (XOR) |
| WhatsApp Meta | HMAC-SHA256 | X-Hub-Signature | request body (sha256=hex) | Yes |
| Signal | HMAC-SHA256 | X-Signal-Signature | request body (hex) | Yes |
CRITICAL: Always use constant-time comparison (XOR loop). Never use === for signature comparison.
IVR Format Comparison
Show equivalent for "speak text, gather DTMF digits, route call" in each format:
Twilio (TwiML):
<Response>
<Gather numDigits="1" action="/api/telephony/captcha-response">
<Say language="en-US">Press 1 to continue</Say>
</Gather>
</Response>
Vonage (NCCO):
[
{ "action": "talk", "text": "Press 1 to continue", "language": "en-US" },
{ "action": "input", "type": ["dtmf"], "dtmf": { "maxDigits": 1 }, "eventUrl": ["/api/telephony/captcha-response"] }
]
Plivo XML:
<Response>
<GetDigits numDigits="1" action="/api/telephony/captcha-response">
<Speak language="en-US">Press 1 to continue</Speak>
</GetDigits>
</Response>
Asterisk (ARI bridge):
{ "actions": [
{ "action": "speak", "text": "Press 1 to continue", "language": "en-US" },
{ "action": "gather", "numDigits": 1, "callbackEvent": "captcha-response" }
]}
Messaging Channels
All messaging goes through MessagingAdapter interface. Supported channels:
| Channel | Location | Adapters |
|---|
| SMS | apps/worker/messaging/sms/ | Twilio, SignalWire, Vonage, Plivo |
| WhatsApp | apps/worker/messaging/whatsapp/ | Meta Cloud API, Twilio wrapper |
| Signal | apps/worker/messaging/signal/ | signal-cli-rest-api bridge |
| Telegram | apps/worker/messaging/telegram/ | Bot API |
| RCS | apps/worker/messaging/rcs/ | Google RBM |
Adding SMS Support
- Create
apps/worker/messaging/sms/provider.ts implementing MessagingAdapter
- Add
case 'provider': in apps/worker/messaging/sms/factory.ts
- SMS adapters reuse telephony credentials — no separate config needed
- Use
hashPhone() from apps/worker/lib/crypto for consistent phone number hashing
WhatsApp Integration
Two modes:
- Meta Cloud API (direct): Graph API v18.0, HMAC-SHA256 webhook validation, media requires separate fetch
- Twilio (wrapper): whatsapp: prefix on phone numbers, delegates to SMS-like API
Common Mistakes
| Mistake | Impact | Fix |
|---|
| Non-constant-time signature comparison | Timing attack vulnerability | Use XOR loop, never === |
| Wrong signature data string | All webhooks rejected | Check provider docs for exact construction |
| Missing phone normalization | Lookup failures | Always E.164 format with + prefix |
| Provider-specific voice codes | Wrong language spoken | Map to provider's language code format |
| Hardcoded Twilio-specific field names | Crashes on other providers | Use adapter method return types |
| Missing bridge service for Asterisk/FreeSWITCH | No self-hosted voice | Deploy sip-bridge with PBX_TYPE env var |