IntegrationsBeta
Twilio
Run VM Hunter on Twilio Programmable Voice calls with a small bridge you host. Twilio streams the callee's audio to the bridge, the bridge gets the verdict from VM Hunter and redirects the live call to your agent TwiML or hangs up.
Beta
The bridge is new. It has been exercised against simulated Twilio Media Streams and the production engine protocol, but not yet at volume on live Twilio traffic. Run it on a test campaign first and tell us what you see at support@vmhunter.com.How it works
Twilio cannot talk to VM Hunter directly: Media Streams delivers base64 μ-law frames inside JSON over a TLS WebSocket, while VM Hunter expects raw 16-bit PCM. The bridge translates between the two and uses the Twilio REST API to steer the call once it has a verdict.
| Step | What happens |
|---|---|
| 1. Call | You create the outbound call with TwiML that starts a Media Stream to your bridge and then pauses. |
| 2. Stream | Twilio opens wss://your-bridge/twilio and sends the callee's audio in 20 ms frames. |
| 3. Classify | The bridge forwards the audio to VM Hunter as PCM and receives AMDSTATUS + AMDCAUSE about two seconds after the answer. |
| 4. Redirect | The bridge updates the call: HUMAN → your HUMAN_URL TwiML, MACHINE → hang up (or your MACHINE_URL). |
Prerequisites
| Requirement | Notes |
|---|---|
| Twilio account | Account SID and Auth Token, used only to redirect the call after classification. |
| A small Linux host | Python 3.8+ with the websockets package. One vCPU handles hundreds of concurrent streams. |
| A public hostname with TLS | Twilio only connects to wss://. Terminate TLS in front of the bridge with Caddy, nginx or a load balancer. |
| API key | From Dashboard → API Keys. |
Install the bridge
Download and install
Terminalwget -N -O vmhunter-twilio.tar.gz https://app.vmhunter.com/vmhunter-twilio.tar.gz mkdir -p /opt/vmhunter-twilio && tar zxvf vmhunter-twilio.tar.gz -C /opt/vmhunter-twilio pip3 install websocketsConfigure
Copy the example environment file and fill in the four required values.
/etc/vmhunter-twilio.envVMHUNTER_API_KEY={YOUR_API_KEY} TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx HUMAN_URL=https://your-app.example.com/twiml/agent # optional: TwiML for machines; leave empty to hang up MACHINE_URL= BRIDGE_HOST=127.0.0.1 BRIDGE_PORT=8765 LOG_FILE=/var/log/vmhunter-twilio.logRun it as a service
Terminalcp /opt/vmhunter-twilio/vmhunter-twilio.service /etc/systemd/system/ systemctl daemon-reload systemctl enable --now vmhunter-twilio tail -f /var/log/vmhunter-twilio.logPut TLS in front
Caddy is the quickest: it fetches and renews the certificate itself. Point a DNS record at the host and use the bundled Caddyfile.
/etc/caddy/Caddyfileamd.example.com { reverse_proxy 127.0.0.1:8765 }With nginx, proxy
/twilioto127.0.0.1:8765withproxy_http_version 1.1and theUpgrade/Connectionheaders set.
Start calls
Create each outbound call with TwiML that starts the stream and then waits. The vid parameter is your lead or call id; it shows up in your call logs. The pause is a fail-safe: if the bridge never redirects the call, Twilio hangs up when it ends.
<Response>
<Start>
<Stream url="wss://amd.example.com/twilio">
<Parameter name="vid" value="lead-12345"/>
</Stream>
</Start>
<Pause length="10"/>
</Response>Passing it inline when you create the call:
curl -X POST https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
--data-urlencode "To=+15551234567" \
--data-urlencode "From=+18005551212" \
--data-urlencode 'Twiml=<Response><Start><Stream url="wss://amd.example.com/twilio"><Parameter name="vid" value="lead-12345"/></Stream></Start><Pause length="10"/></Response>'MachineDetection on these calls. It delays the answer and competes with VM Hunter for the same seconds of audio.Your TwiML
When the verdict is HUMAN, the bridge redirects the call to HUMAN_URL with AMDSTATUS, AMDCAUSE and vid appended as query parameters. Serve whatever connects the caller to an agent:
<Response>
<Dial>
<Queue>sales</Queue>
</Dial>
</Response>For MACHINE the default is <Hangup/>. Set MACHINE_URL to leave a message instead; AMDCAUSE tells you whether it was a beep (MACHINE_BEEP), a greeting still playing (MACHINE) or a disconnected number (DISCONNECT), so you can wait for the beep or skip the drop.
Configuration
| Variable | Default | Purpose |
|---|---|---|
VMHUNTER_API_KEY | — | Your API key. Required. |
TWILIO_ACCOUNT_SID | — | Required. Used to redirect the call. |
TWILIO_AUTH_TOKEN | — | Required. |
HUMAN_URL | — | Required. TwiML URL for live answers. |
MACHINE_URL | hang up | TwiML URL for machines. |
CALLGUARD_AS | HUMAN | How to treat call screening. |
FAILED_AS | MACHINE | Fail-safe when the engine is unreachable or never replies. |
VMHUNTER_WS_URL | ws://app.vmhunter.com:2701 | Engine endpoint. |
BRIDGE_HOST | 0.0.0.0 | Listen address. Use 127.0.0.1 behind a local TLS proxy. |
BRIDGE_PORT | 8765 | Listen port. |
MAX_AUDIO_SEC | 3.5 | Stop forwarding audio after this many seconds. |
REPLY_TIMEOUT_SEC | 8 | Apply FAILED_AS if there is no verdict by then. |
LOG_FILE | stderr | Per-call log. |
Troubleshooting
| Symptom | Check |
|---|---|
| Twilio Debugger shows a Stream WebSocket handshake or connection error | The WebSocket handshake failed. Make sure the hostname resolves, the certificate is valid and the proxy forwards Upgrade headers. Test with curl -i https://amd.example.com/twilio -H "Connection: Upgrade" -H "Upgrade: websocket" and expect a 400 from the bridge, not a proxy error. |
Log shows vmhunter connect failed | The bridge host cannot reach app.vmhunter.com:2701. Test with nc -zv app.vmhunter.com 2701. |
Log shows redirect failed: HTTP 401 | Wrong Account SID or Auth Token. |
Log shows redirect failed: HTTP 404 | The call already ended, usually because the pause in your TwiML was shorter than the round trip. |
| Every call is MACHINE with INITIALSILENCE | The stream is carrying the wrong track. Do not set track on <Stream>; the default inbound track is the callee. |