Skip to main content

Twilio Migration Examples

Common workflows for evaluating a migration, running the adapter, and understanding what the adapter hands back to your app.

Migrate a Twilio voice app

The goal is to move your call traffic to Bandwidth while your app keeps running unchanged.

  1. Run a pre-flight check against your app to see what maps cleanly:

    npm run preflight -- path/to/your/twilio-app

    You get a migration-complexity score and per-feature works-as-is / heads-up / blocker cards. Address any blockers first.

  2. Stand up the adapter with your Bandwidth credentials (see the quickstart guide).

  3. Repoint configuration only:

    • Your Twilio SDK / base URL → the adapter's public URL
    • Your TwiML webhooks → the adapter's public URL
    • A Bandwidth Voice application callback → {PUBLIC_BASE_URL}/bw/initiate
  4. Run your existing app unchanged and place a test call. You'll hear your Twilio app's script spoken by Bandwidth in real time, including greetings, digit menus, and transfers.

Evaluate a migration with the Migration Preflight playground

To evaluate your own call flows before changing any configuration, the adapter generates a single HTML file with no server and no network dependency called the Migration Preflight page:

npm run playground:build # writes dist/playground.html
open dist/playground.html # (macOS) — or just double-click it

Paste your TwiML (or pick a built-in example, like an IVR menu, call recording, dial-to-conference, or a deliberately unsupported flow) and see:

  • a migration-complexity score,
  • a works-as-is / heads-up / blocker breakdown,
  • the translated BXML on demand, and
  • a report you can save or share with your team.
info

The page runs the real translation engine in the browser, so that the verdict you see matches what the adapter does in production, including clearly flagging anything Bandwidth can't do yet.

Drive the REST facade from your Twilio SDK

Once your SDK base URL points at the adapter, backend calls work in Twilio's shape and are translated to Bandwidth over OAuth2. For example, originating a call:

// Your existing Twilio client, base URL pointed at the adapter
const call = await client.calls.create({
to: '+15559876543',
from: '+19195551234',
url: 'https://your-app.example.com/answer', // your TwiML webhook
statusCallback: 'https://your-app.example.com/status',
});

Fetch a call, list its recordings, and download media, all in Twilio's shape:

const fetched = await client.calls(call.sid).fetch();
const recordings = await client.calls(call.sid).recordings.list();
// GET /Recordings/{sid}.mp3 is stream-proxied from Bandwidth

Handle the callbacks the adapter sends you

The adapter also calls you back in Twilio's shape, mapped from Bandwidth's events and signed with X-Twilio-Signature so your existing signature validation passes:

  • Call webhooks — inbound calls and Gather results are POSTed to your TwiML URLs with Twilio's parameter names.
  • Status callback — when a call ends, the StatusCallback from calls.create() receives a completed callback with CallSid, CallStatus, and CallDuration, mapped from Bandwidth's disconnect event.
  • Recording callback — a <Record recordingStatusCallback="…"> maps to Bandwidth's recordingAvailableUrl. When the recording is ready, the adapter forwards a Twilio recordingStatusCallback with RecordingSid, RecordingStatus, and RecordingDuration; RecordingUrl points back at the adapter so a later fetch resolves through it.

Bridge to an AI voice agent

Connect/Start/Stop Stream and Transcription verbs translate to a Media Streams bridge that emulates Twilio's WebSocket protocol. If your Twilio app already streams audio to an AI voice framework, that path keeps working — the frames arrive in the format your framework already expects, now sourced from Bandwidth's network.

When something isn't supported

The adapter never silently mistranslates. If your app uses a verb or operation Bandwidth can't do yet, the adapter returns a clear error instead of degrading the call. Run the pre-flight report first so these surface before you cut over, not on a live call. See the support tables for the full list.