AMD_WS() — AMDY as a native Asterisk module
AMD_WS() is an Asterisk dialplan application that streams the first seconds of an answered call to the AMDY answering machine detection service and sets the same ${AMDSTATUS} / ${AMDCAUSE} variables that Asterisk's built-in AMD() and the AMDY EAGI client (amd.py) set. It replaces the EAGI script on any Asterisk 16, 18, 20+ system, ViciDial/Vicibox or a custom dialer, without Python and without touching your Asterisk build. Current version: v2.0.1.
Using the standard EAGI script instead? See Install AMDY — standard AMD. The EAGI install remains fully supported.
Requirements
| Item | Requirement |
|---|---|
| Asterisk | 16, 18, 20, 21 or 22, with the res_http_websocket module (part of every standard build). Asterisk 13 is not supported. |
| Operating system | Any Linux with gcc, make, pkg-config, tar and curl (the installer adds them). Tested on Vicibox/openSUSE, AlmaLinux/Rocky/CentOS, Debian/Ubuntu. |
| Headers | The installer finds the headers of the running Asterisk. Asterisk is never recompiled or restarted. |
| Network | Outbound TCP to api.amdy.io port 2700 from the telephony server. |
| Optional | MariaDB/MySQL client development files, only for the ViciDial phone/country lookup. |
Install (one command)
Log in as root on the telephony server, then:
curl -fsSL https://raw.githubusercontent.com/nikvb/amd/v2.0.1/install.sh | bash -s -- -y
The installer detects the running Asterisk and its headers, installs the build tools, compiles the module for exactly that Asterisk, backs up any previous app_amd_ws.so, installs and loads the module without hanging up any call, and prints the dialplan snippet. Everything is logged to /var/log/app_amd_ws-install.log. The v2.0.1 tag URL never changes; the sha256 of install.sh is in the release notes.
Install the detection prompt too
Add --playfile to also install AMDY's insert.wav (0.6 s, 8 kHz mono) as /var/lib/asterisk/sounds/amdy/insert.wav; the installer then prints the 8370 line with amdy/insert as the playback argument. --playfile ambiguous.wav installs the 2 s variant; --playfile /path/own.wav installs your own file.
curl -fsSL https://raw.githubusercontent.com/nikvb/amd/v2.0.1/install.sh | bash -s -- -y --playfile
# see what would happen, change nothing
curl -fsSL https://raw.githubusercontent.com/nikvb/amd/v2.0.1/install.sh | bash -s -- --dry-run
# no MySQL dependency, no DB lookup
curl -fsSL https://raw.githubusercontent.com/nikvb/amd/v2.0.1/install.sh | bash -s -- -y --no-db
# remove the module again
curl -fsSL https://raw.githubusercontent.com/nikvb/amd/v2.0.1/install.sh | bash -s -- --uninstall
Verify
asterisk -rx 'module show like app_amd_ws'
asterisk -rx 'core show application AMD_WS'
asterisk -rx 'amd_ws show settings'
Dialplan — ViciDial extension 8370
Replace the EAGI(...amd.py) line of your existing 8370 block; everything else stays:
exten => 8370,1,AGI(agi://127.0.0.1:4577/call_log)
exten => 8370,n,Playback(sip-silence)
exten => 8370,n,AMD_WS(api.amdy.io,2700,${CALLERID(name)},10000)
exten => 8370,n,GotoIf($["${AMDCAUSE}" = "CONNECTION_ERROR" | "${AMDCAUSE}" = "PROCESSING_ERROR" | "${AMDCAUSE}" = "FATAL_ERROR"]?amd_fallback:continue)
exten => 8370,n(amd_fallback),AMD(2000,2000,1000,5000,120,50,4,256)
exten => 8370,n(continue),AGI(VD_amd.agi,${EXTEN})
exten => 8370,n,AGI(agi-VDAD_ALL_outbound.agi,NORMAL-----LB-----${CONNECTEDLINE(name)})
Then asterisk -rx 'dialplan reload'. In ViciDial set the campaign's Routing Extension to 8370, AMD Agent Route Options to ENABLED, and the AMD_AGENT_OPT_<campaign> container entry to exactly HUMAN,HUMAN, then rebuild the telephony server config (see Setup & installation).
Upgrading from 1.x: the error causes changed from NETERR / INTERR to CONNECTION_ERROR / PROCESSING_ERROR / FATAL_ERROR. Update the GotoIf fallback line above, or the fallback to AMD() never runs.
Generic Asterisk (custom dialer)
exten => s,1,Answer()
exten => s,n,AMD_WS(api.amdy.io,2700,${UNIQUEID},10000,,n)
exten => s,n,GotoIf($["${AMDSTATUS}" = "MACHINE"]?machine:human)
exten => s,n(human),Dial(...) ; live person
exten => s,n(machine),Hangup() ; or leave a message
Option n skips the ViciDial database lookup on a box that has none.
Play a prompt during detection
exten => 8370,n,AMD_WS(api.amdy.io,2700,${CALLERID(name)},10000,amdy/insert,d(2000))
Plays the prompt 2 s in while the callee's audio is analysed; playback stops as soon as the verdict is known. Do not add a separate Playback() line for the prompt — it would play before detection starts. File format: see Playback audio (insert.wav).
Call screening (iPhone, Samsung, Google Voice)
When the service reports MACHINE with AMDCAUSE starting with CALLASSISTSCRNAMD, GVOICEAMD or SCREENINGAMD, a screening robot answered. The module exports AMDPHONE, so the dialplan can hold that leg and redial the number once; the second call arrives as call waiting and is routed to an agent as usual. Dialplan block: AMDY docs or vicidial-call-screening.md.
Results — channel variables
| Variable | Values |
|---|---|
AMDSTATUS | HUMAN, MACHINE, NOTSURE, HANGUP |
AMDCAUSE | HUMAN; the service's reply on a machine (e.g. AMD-4.50-0.95, NUMBERSAMD-4.50-0.93); or an error/timeout cause below |
AMDSTATS | <elapsed_ms>-<audio_ms_sent>-<chunks>-<bytes> (ViciDial logs the first number as run_time) |
AMDRESPONSE | The last raw text the service sent |
AMDELAPSED | Milliseconds from the first audio frame to the result |
AMDPHONE, AMDCOUNTRYCODE | The number / country code sent to the service, when known (used to redial after a call-screening verdict) |
| Situation | AMDSTATUS | AMDCAUSE |
|---|---|---|
| Live person | HUMAN | HUMAN |
| Answering machine / voicemail | MACHINE | service reply, e.g. AMD-4.50-0.95 |
| Service unreachable (DNS, firewall, connect timeout) | HUMAN | CONNECTION_ERROR |
| Connection dropped during detection | HUMAN | PROCESSING_ERROR |
| Internal failure on the Asterisk side | HUMAN | FATAL_ERROR |
No verdict within timeout_ms | NOTSURE | SERVER_TIMEOUT |
| No audio received at all (RTP never arrived) | NOTSURE | NOAUDIODATA-<ms> |
| Callee hung up during detection | HANGUP | HANGUP |
| Audio stopped mid-call, service could not finalise | NOTSURE | EOF_INCONCLUSIVE / EOF_ERROR |
Errors default to HUMAN on purpose: a call is never lost because the AMD service was unreachable. NOAUDIODATA-<ms> and HANGUP are the exact values the built-in AMD() uses, so ViciDial's dead-air handling (NOAUDIODATA-Hangup-ENABLED, disposition ADAIR) works unchanged.
How long does a verdict take?
Typically 5–6 seconds after the callee's first audio. The service gives its final answer (including which kind of machine) once it has 4.5 s of speech; leading silence does not count. A human is usually recognised earlier.
Troubleshooting
Health check in one line:
asterisk -rx 'module show like app_amd_ws'; asterisk -rx 'amd_ws show settings' | head -30; timeout 5 bash -c 'exec 3<>/dev/tcp/api.amdy.io/2700' && echo "port 2700 open"
Every call writes two lines to /var/log/asterisk/messages (or full); every line carries vid=, so grep 'vid=V923…' returns one whole call:
AMD_WS: SIP/trunk-0004706c vid=V9231813370204367076 host=api.amdy.io:2700 play=none
AMD_WS: SIP/trunk-0004706c vid=V9231813370204367076 status=MACHINE cause=OTHERAMD-4.50-0.9280 elapsed=6106 sent=96320 chunks=8
AMDCAUSE | What it means | What to check |
|---|---|---|
CONNECTION_ERROR | The server could not reach the service | Outbound TCP 2700 to all api.amdy.io addresses (dig +short api.amdy.io); DNS; module show like res_http_websocket. See Connection problems. |
PROCESSING_ERROR | Connected, then the connection broke | Middleboxes cutting WebSockets; ${AMDRESPONSE}; contact support with the VID |
SERVER_TIMEOUT | Connected, audio sent, no verdict in time | ${AMDRESPONSE}; contact support with the VID |
NOAUDIODATA-<ms> | No audio ever reached Asterisk | RTP/NAT/codec on the trunk; rtp set debug on — not an AMD problem |
FATAL_ERROR | Module-internal (codec, memory, config) | AMD_WS warnings in the log; amd_ws.conf |
| empty variables | AMD_WS() never ran | dialplan show 8370@default; module loaded? |
For a full timeline of one call add option v to AMD_WS() (or trace=yes in /etc/asterisk/amd_ws.conf) and grep AMD_WS:. An empty reply ("") in the trace is the service acknowledging a chunk, not a failure.
Upgrading / uninstalling
Re-run the install command: it builds the new version, backs up the old module and swaps it in without dropping calls. --uninstall removes the module; your dialplan is never modified by the installer.
Full reference
Full guide on the AMDY docs site · github.com/nikvb/amd (v2.0.1 release)