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.

StepWhat happens
1. CallYou create the outbound call with TwiML that starts a Media Stream to your bridge and then pauses.
2. StreamTwilio opens wss://your-bridge/twilio and sends the callee's audio in 20 ms frames.
3. ClassifyThe bridge forwards the audio to VM Hunter as PCM and receives AMDSTATUS + AMDCAUSE about two seconds after the answer.
4. RedirectThe bridge updates the call: HUMAN → your HUMAN_URL TwiML, MACHINE → hang up (or your MACHINE_URL).

Prerequisites

RequirementNotes
Twilio accountAccount SID and Auth Token, used only to redirect the call after classification.
A small Linux hostPython 3.8+ with the websockets package. One vCPU handles hundreds of concurrent streams.
A public hostname with TLSTwilio only connects to wss://. Terminate TLS in front of the bridge with Caddy, nginx or a load balancer.
API keyFrom Dashboard → API Keys.

Install the bridge

  1. Download and install

    Terminal
    wget -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 websockets
  2. Configure

    Copy the example environment file and fill in the four required values.

    /etc/vmhunter-twilio.env
    VMHUNTER_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.log
  3. Run it as a service

    Terminal
    cp /opt/vmhunter-twilio/vmhunter-twilio.service /etc/systemd/system/
    systemctl daemon-reload
    systemctl enable --now vmhunter-twilio
    tail -f /var/log/vmhunter-twilio.log
  4. Put 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/Caddyfile
    amd.example.com {
        reverse_proxy 127.0.0.1:8765
    }

    With nginx, proxy /twilio to 127.0.0.1:8765 with proxy_http_version 1.1 and the Upgrade / Connection headers 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.

TwiML
<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
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>'
Do not enable Twilio's own 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:

HUMAN_URL response
<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

VariableDefaultPurpose
VMHUNTER_API_KEYYour API key. Required.
TWILIO_ACCOUNT_SIDRequired. Used to redirect the call.
TWILIO_AUTH_TOKENRequired.
HUMAN_URLRequired. TwiML URL for live answers.
MACHINE_URLhang upTwiML URL for machines.
CALLGUARD_ASHUMANHow to treat call screening.
FAILED_ASMACHINEFail-safe when the engine is unreachable or never replies.
VMHUNTER_WS_URLws://app.vmhunter.com:2701Engine endpoint.
BRIDGE_HOST0.0.0.0Listen address. Use 127.0.0.1 behind a local TLS proxy.
BRIDGE_PORT8765Listen port.
MAX_AUDIO_SEC3.5Stop forwarding audio after this many seconds.
REPLY_TIMEOUT_SEC8Apply FAILED_AS if there is no verdict by then.
LOG_FILEstderrPer-call log.

Troubleshooting

SymptomCheck
Twilio Debugger shows a Stream WebSocket handshake or connection errorThe 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 failedThe bridge host cannot reach app.vmhunter.com:2701. Test with nc -zv app.vmhunter.com 2701.
Log shows redirect failed: HTTP 401Wrong Account SID or Auth Token.
Log shows redirect failed: HTTP 404The call already ended, usually because the pause in your TwiML was shorter than the round trip.
Every call is MACHINE with INITIALSILENCEThe stream is carrying the wrong track. Do not set track on <Stream>; the default inbound track is the callee.