Twilio Voice Compatibility Adapter
The Twilio Voice Compatibility Adapter lets an existing Twilio voice application run on Bandwidth's network by changing one URL with no application rewrite. It's a live translator: it speaks Twilio's 2010-04-01 REST shape, TwiML, and webhook payloads on the front, and the Bandwidth Voice API on the back.
The everyday call-flow building blocks are proven end-to-end on real Twilio and Bandwidth calls. The status labels throughout these pages tell you exactly what's verified live, what's covered by automated tests, and what's still experimental, so you always know what you can rely on.
How it works
A phone call on either platform is driven by a small script the network reads step by step — "say a greeting → wait for a key press → if they press 1, transfer to sales." Twilio writes that script in TwiML; Bandwidth uses BXML for the same ideas. Roughly 80% maps one-to-one; the adapter handles the rest.
The network doesn't get the whole script at once. Instead, it asks the app "what now?" one step at a time. The adapter sits in the middle of that conversation:
- A call hits a Bandwidth number → Bandwidth asks the adapter, in BXML, "what now?"
- The adapter asks your unmodified Twilio app the same question, signing the request the way Twilio would, so your app trusts it.
- Your app replies with TwiML → the adapter translates it to BXML on the spot and hands it back.
- Caller input (DTMF or speech) loops back through the same path for the life of the call.
The translation is deterministic and driven by a single compatibility matrix, not an AI guessing. When your app uses something the adapter can't do yet, it fails loudly with a clear error rather than mistranslating a live call silently.
What you can do
TwiML verbs
| Verb | Support | Notes |
|---|---|---|
Say, Play, Pause, Hangup, Redirect | ✅ | SSML touches supported on Say |
Gather | ✅ | DTMF + speech; speech input needs enablement on your Bandwidth account |
Record | ✅ | action → recordCompleteUrl; recordingStatusCallback → recordingAvailableUrl |
Dial → Number, Sip | ✅ | callerId, timeout, action mapped |
Dial → Conference | ⚠️ | Mute + event callbacks; no hold music or start/end lifecycle |
Connect/Start/Stop (Stream, Transcription) | ✅ | Media Streams bridge emulates Twilio's WebSocket protocol |
Reject, Refer | ⚠️ | Reject → Hangup (with warning); Refer is inbound-SIP only |
Queue, Client, Enqueue, Leave, Pay | ❌ | No Bandwidth equivalent — the adapter fails loudly |
REST API facade
Backend calls your Twilio SDK makes are served in Twilio's shape under /2010-04-01/Accounts/{accountSid} and translated to Bandwidth.
| Twilio SDK call | Adapter endpoint | Status |
|---|---|---|
calls.create() | POST /Calls.json | ✅ |
calls(sid).update() | POST /Calls/{sid}.json | ✅ hang up / redirect |
calls(sid).fetch() | GET /Calls/{sid}.json | ✅ |
calls(sid).recordings.list() | GET /Calls/{sid}/Recordings.json | ✅ |
recordings(sid).fetch() / media | GET /Recordings/{sid}.json / .mp3 / .wav | ✅ |
recordings(sid).update({status}) | POST /Calls/{sid}/Recordings/{recSid}.json | ✅ pause/resume (stopped unsupported) |
Errors mirror Twilio: unknown resources return 20404, bad credentials 20003, missing params 21201/21205/21213.
The full machine-readable surface is published as an OpenAPI spec at openapi/twilio-compat-adapter.yml in the adapter repo. It intentionally mirrors Twilio's API paths, JSON shapes, and error codes, so it diverges from Bandwidth API Standards v3 by design — byte-level Twilio compatibility is the whole point.
What you can't do yet
- Call queues ("you're caller number 3, please hold") — Bandwidth has no queue primitive to translate to. The most significant current limitation.
- Conference hold music — no Bandwidth equivalent.
- Stopping a recording over REST — Bandwidth pauses and resumes over REST but has no REST stop (
StopRecordingis a BXML verb), soStatus=stoppedfails loudly. - Number provisioning — the adapter does not provision numbers. Ordering, activation, and release are handled by the Bandwidth
bandCLI (https://dev.bandwidth.com/tools/cli/).
In every case, the adapter surfaces a clear error rather than silently mistranslating. You can trust us to let you know where the gaps are.
What you need to get started
- A Bandwidth App account with Voice enabled
- Not a Bandwidth user yet? Start a free trial for Bandwidth Build
- OAuth2 client credentials (client ID and client secret) from The Bandwidth App, plus a Bandwidth Voice application ID
- An existing Twilio voice application you want to migrate
- Node.js 20+ to run the adapter
Once you have credentials, head to the quickstart guide to run the adapter and point your app at it.