Twilio integration
This is a reference implementation — you clone it, run it, and adapt it. It's not a hosted service or a Twilio Marketplace add-on.
Twilio lets you make and receive phone calls from your code. Its Media Streams feature sends a live copy of any call's audio to a URL you control — perfect for real-time transcription, without touching the call itself.
This guide walks you through a small proxy that takes that audio stream and feeds it into Speechmatics Realtime, printing per-speaker transcripts to your console as the call unfolds. The reference repo ships the same proxy in two languages — Python and Node.js — so pick whichever fits your team.
How it works
Here's what runs where before we get to the commands:
- Caller A dials your Twilio number which forwards to Caller B — the two talk like a direct call.
- Twilio sends a live copy of the audio to your proxy over a WebSocket.
- The proxy hands that audio to Speechmatics with channel diarization turned on.
- Each speaker (
inboundfrom Caller A,outboundfrom Caller B) gets its own transcript stream. - Speechmatics returns transcripts, and the proxy prints them under per-channel labels.
Everything runs on your machine. ngrok gives Twilio a public URL to reach you.
Before you begin
Tick these off as you gather them:
- A Speechmatics API key from the Speechmatics portal.
- A Twilio account, a voice-capable Twilio phone number, your Account SID, and your Auth Token.
- A second phone number you own or have verified with Twilio to call.
- An ngrok account and auth token.
- Python 3.9+ or Node.js 20+, depending on which implementation you pick.
Clone the reference repo
Both implementations live in one repo. Clone it and change into the folder:
git clone https://github.com/speechmatics/speechmatics-twilio-integration.git
cd speechmatics-twilio-integration
Inside, you'll find a python/ folder and a js/ folder alongside a shared .env.example at the root. Both implementations read from the same .env, so you set your credentials once and can flip between languages freely.
Set your credentials
Copy the template:
cp .env.example .env
Open .env in your editor and the environment variables. The proxy won't run if any are missing.
Install and run
Move into python/ and set up a virtualenv:
cd python
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Start the proxy:
python proxy.py
Move into js/ and install dependencies:
cd js
npm install
Start the proxy:
npm start
You'll see a banner like this:
Listening on ws://localhost:5000/twilio
Twilio number +XX... pointed at wss://abc123.../twilio. Call it now.
Make your first call
With the proxy running, dial your Twilio number from any phone.
- Twilio answers the incoming call, plays the short greeting, then dials
FORWARD_TO_NUMBER. - Answer the second phone. You're now on a normal two-way call.
- Speak into either handset. Transcripts appear in the console within seconds:
[twilio] incoming Media Streams connection
[twilio] call started - stream=MZxxx... call=CAxxx... tracks=['inbound', 'outbound']
[inbound ] Hello there. How are you doing today?
[outbound] I'm doing pretty well thanks.
[twilio] call stopped (stream=MZxxx...)
- inbound — whoever dialed your Twilio number.
- outbound — whoever answered
FORWARD_TO_NUMBER.
When you're done, press Ctrl-C in the terminal to stop the proxy. Your Twilio number's Voice URL stays pointed at the last ngrok tunnel — the next run overwrites it, so you never have to touch the Twilio Console after the first time.
What the proxy does under the hood
Once a call arrives, the proxy has three jobs:
- Receive Twilio's audio. Twilio sends 20 ms of mulaw-encoded 8 kHz mono audio at a time — 160 bytes per frame. Each frame arrives on the WebSocket as a JSON
mediaevent with a base64payloadand atracklabel (inboundoroutbound). The proxy uses that label to keep the two speakers apart. - Forward the audio to Speechmatics as-is. Speechmatics Realtime accepts mulaw 8 kHz directly, so the proxy passes bytes straight through without re-encoding. That keeps latency down and avoids audio-quality loss.
- Ask Speechmatics for per-channel transcripts. The session is configured with
diarization: "channel"and channel labels["inbound", "outbound"]. Every transcript that comes back carries achannelfield, and the proxy prints it under the matching label.
Troubleshooting
Still stuck? See Twilio support or contact Speechmatics.