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

RequirementNotes
FreeSWITCH 1.10+With mod_lua loaded (it is in the default build).
An audio streaming moduleEither 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 keyFrom Dashboard → API Keys.
NetworkOutbound TCP to app.vmhunter.com:2701.
If neither module is installed, 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

  1. Download the client

    Two files: the Lua script and an XML dialplan include.

    Terminal
    wget -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/scripts and /usr/local/freeswitch/conf/dialplan/default. The XML goes in whichever context your dialer originates into.

  2. Set your API key

    The script reads channel variables first and global variables second, so you can set the key once in vars.xml or 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=ws://app.vmhunter.com:2701"/>
  3. Reload

    Terminal
    fs_cli -x "reloadxml"
  4. Originate calls into the AMD extension

    Send the customer leg to vmhunter_amd so detection starts the moment the callee answers. vid is the lead or call id that VM Hunter bills and records under; it falls back to the channel UUID.

    fs_cli
    originate {vid=12345,ignore_early_media=true,origination_caller_id_number=18005551212}sofia/gateway/carrier/15551234567 vmhunter_amd XML default

    Watch the result in the console:

    Terminal
    fs_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.

vmhunter_amd.xml — the parts you edit
<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>
Conditions in FreeSWITCH are evaluated before any action runs, which is why the script transfers to a second extension instead of branching in place. Keep that pattern if you restructure the dialplan.

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:

vmhunter_amd.xml — vmhunter_route
<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}.

Check the TCPA and prerecorded-message rules that apply to your campaigns before playing recordings to screened calls.

Configuration

Variables the script reads (channel variable first, then global):

VariableDefaultPurpose
vmhunter_api_keyYour API key. Required.
vmhunter_ws_urlws://app.vmhunter.com:2701Engine endpoint.
vidchannel UUIDLead or call id shown in your call logs.
vmhunter_moduleautoaudio_fork or audio_stream. Only needed if both modules are loaded.

Two constants at the top of the script control the fail-safes:

ConstantDefaultPurpose
CALLGUARD_ASHUMANWhat AMDSTATUS becomes when the raw verdict is CALLGUARD.
FAILED_ASMACHINEWhat AMDSTATUS becomes when the engine errors or never replies.
WAIT_MS6000How long to wait for a verdict before applying FAILED_AS.

Channel variables set on every call:

VariableValues
AMDSTATUSHUMAN or MACHINE. Route on this one.
AMDSTATUS_RAWThe engine's untouched verdict: HUMAN, MACHINE, CALLGUARD or FAILED.
AMDCAUSEThe reason, e.g. MACHINE_BEEP, INITIALSILENCE, CALLGUARD_PHRASE:…. Full list in the API reference.

Troubleshooting

SymptomCheck
AMDCAUSE=NOT_RUNvmhunter_api_key is empty. Set it in vars.xml or the originate string.
AMDCAUSE=STREAM_START_FAILEDThe 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_FAILEDThe module could not reach the engine. Test with nc -zv app.vmhunter.com 2701.
AMDCAUSE=NO_REPLYNo verdict within WAIT_MS. Check for packet loss on the WebSocket and that the callee leg is actually answered.
AMDCAUSE=AUTH_FAILEDThe key is wrong or was regenerated.