Integrations

VICIdial / Asterisk

An EAGI client that streams the callee's audio to VM Hunter and sets AMDSTATUS and AMDCAUSE on the channel. It drops straight into a VICIdial routing extension, and works on any Asterisk dialplan.

Prerequisites

RequirementNotes
VICIdial or AsteriskAny Asterisk with EAGI support (VICIdial ships it). Root access to edit the dialplan.
Python 3With the websocket-client package. Python 3.6 or newer is fine.
API keyFrom Dashboard → API Keys.
NetworkOutbound TCP to app.vmhunter.com:2701 from every dialer server.

Install

  1. Install Python and the WebSocket library

    VICIdial runs on openSUSE, so the example uses zypper. Use apt or yum on other systems.

    Terminal
    zypper in -y python3-pip
    pip3 install --upgrade pip
    pip3 install websocket-client
  2. Download the client

    Unpack vmhunter.agi.py into the Asterisk AGI directory. You can also download it from the dashboard.

    Terminal
    wget -N -O vmhunter.tar.gz https://app.vmhunter.com/vmhunter.tar.gz
    tar zxvf vmhunter.tar.gz --directory /var/lib/asterisk/agi-bin
    chmod +x /var/lib/asterisk/agi-bin/vmhunter.agi.py
  3. Set your API key

    The script reads its settings from environment variables. Asterisk must see them, so put them where its process picks them up: /etc/environment on most installs, or the unit's Environment= lines if Asterisk runs under systemd. Restart Asterisk after editing.

    /etc/environment
    AMD_WS_API_KEY="{YOUR_API_KEY}"
    AMD_LOG_FILE="/var/log/vmhunter.agi.log"
    To confirm Asterisk can see the variable: cat /proc/$(pidof asterisk)/environ | tr '\0' '\n' | grep AMD_
  4. Add the routing extension

    Add this to the context your campaigns use (/etc/asterisk/extensions.conf on VICIdial). It runs VM Hunter, then hands the verdict to VICIdial's own VD_amd.agi, which drops the call when AMDSTATUS=MACHINE.

    /etc/asterisk/extensions.conf
    exten => 8370,1,AGI(agi://127.0.0.1:4577/call_log)
    exten => 8370,n,Playback(sip-silence)
    exten => 8370,n,EAGI(/var/lib/asterisk/agi-bin/vmhunter.agi.py,${VID})
    exten => 8370,n,NoOp(VMHUNTER AMDSTATUS=${AMDSTATUS} RAW=${AMDSTATUS_RAW} CAUSE=${AMDCAUSE})
     ; optional: give disconnected numbers their own disposition so the lead is not recycled
    exten => 8370,n,GotoIf($["${AMDCAUSE}"="DISCONNECT"]?disc)
    exten => 8370,n,AGI(VD_amd.agi,${EXTEN})
    exten => 8370,n,AGI(agi-VDAD_ALL_outbound.agi,NORMAL-----LB-----${CONNECTEDLINE(name)})
    exten => 8370,n,Hangup()
    exten => 8370,n(disc),AGI(agi-VDAD_ALL_outbound.agi,DISCONNECT-----LB-----${CONNECTEDLINE(name)})
    exten => 8370,n,Hangup()

    Plain Asterisk without VICIdial: replace the two AGI(...) lines with your own branch on ${AMDSTATUS}, for example GotoIf($["${AMDSTATUS}"="HUMAN"]?agent:hangup).

  5. Reload and test

    Terminal
    asterisk -rx "dialplan reload"
    tail -f /var/log/vmhunter.agi.log

    Place a test call. Each call logs a line with the verdict, the cause and how long the round trip took. The same call appears in your call logs with the transcript and a recording of what the engine heard.

VICIdial campaign settings

Point each outbound campaign at the new extension. Nothing else in the campaign changes.

SettingValue
AMD Routing Extension8370
AMD TypeAMD
AMD MethodEAGI

Roll out one campaign first

Keep production campaigns on your current extension, move one campaign to 8370, and compare its answering-machine and dead-air rates for a day before moving the rest.

Call screening (CALLGUARD)

iPhone's “Ask reason for calling”, Google Call Screen and similar features answer the call with a prompt and let a real person listen. VM Hunter labels those calls AMDCAUSE=CALLGUARD_PHRASE:<phrase>. By default the client maps them to AMDSTATUS=HUMAN so they reach an agent.

A better experience is to play a short recorded introduction first, so the screener hears who is calling, then connect the agent. The 8371 extension below does that; use it for one test campaign.

extensions.conf — screened calls hear an intro
exten => 8371,1,AGI(agi://127.0.0.1:4577/call_log)
exten => 8371,n,Playback(sip-silence)
exten => 8371,n,EAGI(/var/lib/asterisk/agi-bin/vmhunter.agi.py,${VID})
exten => 8371,n,NoOp(VMHUNTER AMDSTATUS=${AMDSTATUS} RAW=${AMDSTATUS_RAW} CAUSE=${AMDCAUSE})
exten => 8371,n,GotoIf($["${AMDCAUSE:0:9}" = "CALLGUARD"]?callguard)
exten => 8371,n,AGI(VD_amd.agi,${EXTEN})
exten => 8371,n,AGI(agi-VDAD_ALL_outbound.agi,NORMAL-----LB-----${CONNECTEDLINE(name)})
exten => 8371,n,Hangup()
exten => 8371,n(callguard),Playback(vmhunter-callguard-intro)
exten => 8371,n,Set(AMDSTATUS=HUMAN)
exten => 8371,n,AGI(VD_amd.agi,${EXTEN})
exten => 8371,n,AGI(agi-VDAD_ALL_outbound.agi,NORMAL-----LB-----${CONNECTEDLINE(name)})
exten => 8371,n,Hangup()

Record the intro as 8 kHz mono WAV at /var/lib/asterisk/sounds/vmhunter-callguard-intro.wav, three to five seconds, naming your company and the reason for the call:

Terminal
sox intro.mp3 -r 8000 -c 1 -b 16 -e signed-integer /var/lib/asterisk/sounds/vmhunter-callguard-intro.wav
If the sound file is missing, Playback fails and Asterisk hangs up the call. Check the TCPA and prerecorded-message rules that apply to your campaigns before playing recordings to screened calls.

Configuration

All settings are environment variables. Only the API key is required.

VariableDefaultPurpose
AMD_WS_API_KEYYour API key. Required.
AMD_WS_URLws://app.vmhunter.com:2701Engine endpoint.
AMD_AUDIO_SECONDS3.5Maximum audio streamed. The engine normally answers at about 2.3 s; the client stops as soon as the reply arrives.
AMD_CALLGUARD_STATUSHUMANHow to map screened calls: HUMAN connects, MACHINE drops, CALLGUARD passes the raw status through for your own branch.
AMD_FAILED_STATUSMACHINEFail-safe status when the engine is unreachable or rejects the call.
AMD_LOG_FILE/var/log/vmhunter.agi.logPer-call log.
AMD_DEBUG0Set to 1 for verbose logging.

Channel variables set on every call:

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

Troubleshooting

SymptomCheck
AMDCAUSE=AUTH_FAILEDAsterisk does not see AMD_WS_API_KEY, or the key was regenerated. Restart Asterisk after changing the environment.
AMDCAUSE=LIMIT_REACHEDThe monthly allowance is used up. Upgrade in Billing; the counter resets each period. PERIOD_EXPIRED and NO_SUBSCRIPTION mean the subscription needs attention in Billing too.
AMDCAUSE=SPEECHLLM_ENGINE_UNAVAILABLEThe engine could not process the call. It is not billed; the client applies AMD_FAILED_STATUS.
AMDCAUSE=CPS_LIMIT or CHANNEL_LIMITYour dialer is starting calls faster, or holding more at once, than your plan allows. Not billed. Lower the campaign's dial level or upgrade; set AMD_FAILED_STATUS=HUMAN so these calls reach an agent instead of being dropped.
Every call is MACHINE with INITIALSILENCENo audio reaches the script. Make sure the extension uses EAGI, not AGI, and that the call is answered before the extension runs.
Connection errors in the logOutbound TCP 2701 is blocked. Test with nc -zv app.vmhunter.com 2701.