Integrations
FreeSWITCH
A Lua script that forks the callee's audio to VM Hunter through mod_audio_fork or mod_audio_stream, then sets AMDSTATUS and AMDCAUSE on the channel. A drop-in XML dialplan routes the call on the result.
Prerequisites
| Requirement | Notes |
|---|---|
| FreeSWITCH 1.10+ | With mod_lua loaded (it is in the default build). |
| An audio streaming module | Either mod_audio_fork (drachtio) or mod_audio_stream. Both send 8 kHz mono PCM over a WebSocket, which is exactly what VM Hunter expects. The script detects whichever one is loaded. |
| API key | From Dashboard → API Keys. |
| Network | Outbound TCP 443 to app.vmhunter.com (or 2701 for the unencrypted endpoint). |
mod_audio_stream is the lighter build: it has no dependencies beyond libwebsockets. Follow its README, then add <load module="mod_audio_stream"/> to modules.conf.xml.Install
Download the client
Two files: the Lua script and an XML dialplan include. Source and issues on GitHub.
Terminalwget -N -O vmhunter-freeswitch.tar.gz https://app.vmhunter.com/vmhunter-freeswitch.tar.gz tar zxvf vmhunter-freeswitch.tar.gz cp vmhunter_amd.lua /usr/share/freeswitch/scripts/ cp vmhunter_amd.xml /etc/freeswitch/dialplan/default/Paths vary by packaging: on source builds use
/usr/local/freeswitch/scriptsand/usr/local/freeswitch/conf/dialplan/default. The XML goes in whichever context your dialer originates into.Set your API key
The script reads channel variables first and global variables second, so you can set the key once in
vars.xmlor pass it per call in the originate string.conf/vars.xml<X-PRE-PROCESS cmd="set" data="vmhunter_api_key={YOUR_API_KEY}"/> <!-- optional --> <X-PRE-PROCESS cmd="set" data="vmhunter_ws_url=wss://app.vmhunter.com/ws"/>Reload
Terminalfs_cli -x "reloadxml"Originate calls into the AMD extension
Send the customer leg to
vmhunter_amdso detection starts the moment the callee answers.vidis the lead or call id that VM Hunter bills and records under; it falls back to the channel UUID.fs_clioriginate {vid=12345,ignore_early_media=true,origination_caller_id_number=18005551212}sofia/gateway/carrier/15551234567 vmhunter_amd XML defaultWatch the result in the console:
Terminalfs_cli /log 6 # [vmhunter] uuid=... vid=12345 module=audio_stream raw=MACHINE -> status=MACHINE cause=MACHINE_BEEP waited=2300ms
Routing the verdict
The bundled dialplan runs the script, then transfers to vmhunter_route, which sends the call on to vmhunter_HUMAN or vmhunter_MACHINE. Edit those two extensions for your setup; everything above them can stay as it is.
<extension name="vmhunter_HUMAN">
<condition field="destination_number" expression="^vmhunter_HUMAN$">
<!-- hand the live person to your agents: a callcenter queue, a fifo, or a bridge -->
<action application="callcenter" data="sales@default"/>
</condition>
</extension>
<extension name="vmhunter_MACHINE">
<condition field="destination_number" expression="^vmhunter_MACHINE$">
<!-- disconnected numbers get their own disposition so the dialer does not recycle the lead -->
<condition field="${AMDCAUSE}" expression="^DISCONNECT">
<action application="set" data="dialer_disposition=DISCONNECT"/>
<anti-action application="set" data="dialer_disposition=MACHINE"/>
</condition>
<action application="hangup" data="NORMAL_CLEARING"/>
</condition>
</extension>Call screening (CALLGUARD)
Screened calls (iPhone “Ask reason for calling”, Google Call Screen) are labelled AMDCAUSE=CALLGUARD_PHRASE:<phrase>. The bundled vmhunter_route extension plays a short intro so the screener hears who is calling, marks the call HUMAN and connects an agent:
<extension name="vmhunter_route">
<condition field="destination_number" expression="^vmhunter_route$"/>
<condition field="${AMDCAUSE}" expression="^CALLGUARD">
<action application="playback" data="$${sounds_dir}/vmhunter/callguard-intro.wav"/>
<action application="set" data="AMDSTATUS=HUMAN"/>
<action application="transfer" data="vmhunter_HUMAN XML default"/>
<anti-action application="transfer" data="vmhunter_${AMDSTATUS} XML default"/>
</condition>
</extension>Record the intro as 8 kHz mono WAV at sounds/vmhunter/callguard-intro.wav. To keep dropping screened calls instead, delete the vmhunter_route extension and transfer straight to vmhunter_${AMDSTATUS}.
Configuration
Variables the script reads (channel variable first, then global):
| Variable | Default | Purpose |
|---|---|---|
vmhunter_api_key | — | Your API key. Required. |
vmhunter_ws_url | wss://app.vmhunter.com/ws | Engine endpoint. |
vid | channel UUID | Lead or call id shown in your call logs. |
vmhunter_module | auto | audio_fork or audio_stream. Only needed if both modules are loaded. |
Two constants at the top of the script control the fail-safes:
| Constant | Default | Purpose |
|---|---|---|
CALLGUARD_AS | HUMAN | What AMDSTATUS becomes when the raw verdict is CALLGUARD. |
FAILED_AS | MACHINE | What AMDSTATUS becomes when the engine errors or never replies. |
WAIT_MS | 6000 | How long to wait for a verdict before applying FAILED_AS. |
Channel variables set on every call:
| Variable | Values |
|---|---|
AMDSTATUS | HUMAN or MACHINE. Route on this one. |
AMDSTATUS_RAW | The engine's untouched verdict: HUMAN, MACHINE, CALLGUARD or FAILED. |
AMDCAUSE | The reason, e.g. MACHINE_BEEP, INITIALSILENCE, CALLGUARD_PHRASE:…. Full list in the API reference. |
Troubleshooting
| Symptom | Check |
|---|---|
AMDCAUSE=NOT_RUN | vmhunter_api_key is empty. Set it in vars.xml or the originate string. |
AMDCAUSE=STREAM_START_FAILED | The streaming module is not loaded, or its uuid_audio_fork / uuid_audio_stream command failed. Run fs_cli -x "module_exists mod_audio_stream". |
AMDCAUSE=WS_CONNECT_FAILED | The module could not reach the engine. Test with nc -zv app.vmhunter.com 443. If your streaming module was built without TLS, set vmhunter_ws_url=ws://app.vmhunter.com:2701. |
AMDCAUSE=NO_REPLY | No verdict within WAIT_MS. Check for packet loss on the WebSocket and that the callee leg is actually answered. |
AMDCAUSE=WS_ERROR with "WebSocket upgrade failed" | The module reached port 443 but did not speak TLS, so the server answered 400 to a plain-text request. Confirm your mod_audio_stream is the TLS build (ldd mod_audio_stream.so | grep ssl); until it is, use ws://app.vmhunter.com:2701. The script logs the module's error text at ERR level. |
Every call INITIALSILENCE, verdict after 4 to 5 s | The engine received silence. Scripts before 2026-10-24 waited with a blocking con:pop(1, ms), which stops the session reading media, so the fork streamed nothing; update to the current vmhunter_amd.lua (con:pop(0) + session:sleep()). Otherwise the fork is on the wrong leg. |
AMDCAUSE=AUTH_FAILED | The key is wrong or was regenerated. |