AMDY.IO

AMD_WS() — native Asterisk module

updated 2026-09-27

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

ItemRequirement
Asterisk16, 18, 20, 21 or 22, with the res_http_websocket module (part of every standard build). Asterisk 13 is not supported.
Operating systemAny Linux with gcc, make, pkg-config, tar and curl (the installer adds them). Tested on Vicibox/openSUSE, AlmaLinux/Rocky/CentOS, Debian/Ubuntu.
HeadersThe installer finds the headers of the running Asterisk. Asterisk is never recompiled or restarted.
NetworkOutbound TCP to api.amdy.io port 2700 from the telephony server.
OptionalMariaDB/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

VariableValues
AMDSTATUSHUMAN, MACHINE, NOTSURE, HANGUP
AMDCAUSEHUMAN; 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)
AMDRESPONSEThe last raw text the service sent
AMDELAPSEDMilliseconds from the first audio frame to the result
AMDPHONE, AMDCOUNTRYCODEThe number / country code sent to the service, when known (used to redial after a call-screening verdict)
SituationAMDSTATUSAMDCAUSE
Live personHUMANHUMAN
Answering machine / voicemailMACHINEservice reply, e.g. AMD-4.50-0.95
Service unreachable (DNS, firewall, connect timeout)HUMANCONNECTION_ERROR
Connection dropped during detectionHUMANPROCESSING_ERROR
Internal failure on the Asterisk sideHUMANFATAL_ERROR
No verdict within timeout_msNOTSURESERVER_TIMEOUT
No audio received at all (RTP never arrived)NOTSURENOAUDIODATA-<ms>
Callee hung up during detectionHANGUPHANGUP
Audio stopped mid-call, service could not finaliseNOTSUREEOF_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
AMDCAUSEWhat it meansWhat to check
CONNECTION_ERRORThe server could not reach the serviceOutbound TCP 2700 to all api.amdy.io addresses (dig +short api.amdy.io); DNS; module show like res_http_websocket. See Connection problems.
PROCESSING_ERRORConnected, then the connection brokeMiddleboxes cutting WebSockets; ${AMDRESPONSE}; contact support with the VID
SERVER_TIMEOUTConnected, audio sent, no verdict in time${AMDRESPONSE}; contact support with the VID
NOAUDIODATA-<ms>No audio ever reached AsteriskRTP/NAT/codec on the trunk; rtp set debug on — not an AMD problem
FATAL_ERRORModule-internal (codec, memory, config)AMD_WS warnings in the log; amd_ws.conf
empty variablesAMD_WS() never randialplan 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)