Docs / FreeSWITCH integration
Integrate AMDY AMD with FreeSWITCH
AMDY.IO AI answering machine detection for FreeSWITCH uses mod_audio_fork to stream answered-call audio to the AMDY WebSocket endpoint at ws://api.amdy.io:2700 and route each call on the classification result. The same backend powers the ViciDial and Asterisk integrations, so results, timing, and your dashboard stay consistent.
What does this integration do?
The integration attaches an audio fork to every answered call, streams mono 8 kHz 16-bit PCM to the AMDY detection service, and waits for a classification. The result is exposed as channel variables that your dialplan branches on. Only HUMAN results reach an agent; everything else is dispositioned automatically.
mod_audio_fork cannot send custom HTTP headers on the WebSocket upgrade, so fork connections carry no X-API-Key. AMDY authorizes fork connections by IP allowlist instead. The installer registers this host's public IP against your account using your API key.
What do you need before you start?
- FreeSWITCH with mod_audio_fork available (see note below)
- Root access on the FreeSWITCH server
- Your AMDY.IO API key, from Settings
- Outbound TCP to api.amdy.io:2700
There is no verified prebuilt download URL for mod_audio_fork. The upstream drachtio repository was deleted. The installer accepts a prebuilt .so via --module (recommended), or builds from source when a configured FreeSWITCH source tree is available via --fs-src. If neither path works, the installer fails with instructions rather than downloading from an unverified source.
How do you install the AMDY FreeSWITCH integration?
Run the installer as root with your API key:
bash installamd-freeswitch.sh <API_KEY> [options]
Or pass a prebuilt module and a key together:
bash installamd-freeswitch.sh --api-key <KEY> --module /path/to/mod_audio_fork.so
Key installer options:
| Flag | Purpose |
|---|---|
| --module PATH | Install this prebuilt mod_audio_fork.so (recommended) |
| --fs-src PATH | FreeSWITCH source tree (configured) to build mod_audio_fork from source |
| --source-repo URL | Git repo with mod_audio_fork source (default: mdslaney/drachtio-freeswitch-modules clone) |
| --dialplan PATH | Path to amdy-fork.xml (default: embedded copy in the installer) |
| --skip-register | Skip IP registration with the portal |
| --skip-build | Do not attempt the build-from-source path |
The installer:
- Installs and loads mod_audio_fork in modules.conf.xml
- Drops the amdy-fork.xml dialplan snippet and the amdy_fork.lua driver script
- Stores the API key in /etc/amdy/api-key (mode 600)
- Registers this server's public IP with your AMDY account via POST /api/v1/ips/register
How do you verify the install?
The health check script confirms six conditions in one run: FreeSWITCH is running, the event socket responds, mod_audio_fork is loaded, the amdy dialplan context is parsed, api.amdy.io:2700 is reachable, and the API key file exists.
bash check-amdy-freeswitch.sh
Or confirm the module manually. You should see true:
fs_cli -x "module_exists mod_audio_fork"
The check script exits 0 when all checks pass and 1 otherwise. It is safe to run repeatedly; it only reads state and never modifies the dialplan or module files.
What does the dialplan do on answer?
- Answers the call. Media must be flowing before the fork attaches.
- Runs amdy_fork.lua, which starts the audio fork:
uuid_audio_fork <uuid> start ws://api.amdy.io:2700 mono 8k {"config":{"sample_rate":8000,"VID":"<caller_id_name or Unknown>"}}- The config frame carries the call identity. VID maps to caller_id_name in FreeSWITCH (equivalent to agi_calleridname in Asterisk). If empty, AMDY records it as Unknown.
- The Lua driver waits up to amdy_max_wait_ms (default 10000, clamped 1000–30000) for a classification from the server. It listens for mod_audio_fork::transcription and mod_audio_fork::json events filtered by the channel UUID.
- When the deadline passes or a result arrives, the driver stops the fork by sending {"eof":1} as the final text frame, then sets channel variables for the dialplan to branch on.
- The dialplan transfers to amdy_dispatch_${amdy_result}, which routes each result to its own extension.
What result values does AMDY return and how are they routed?
| amdy_result | Action | Hangup cause |
|---|---|---|
| HUMAN | Bridge to amdy_bridge_target | NORMAL_CLEARING (if bridge fails) |
| MACHINE | Transfer to amdy_voicemail_extension in amdy_voicemail_context | — |
| FAS | Hangup | CALL_REJECTED |
| TIMEOUT | Hangup | RECOVERY_ON_TIMER_EXPIRE |
| ERROR | Hangup | NETWORK_OUT_OF_ORDER |
Before routing, set amdy_bridge_target, amdy_voicemail_extension, and amdy_voicemail_context on the channel. If HUMAN fires with no bridge target set, the call hangs up with INCOMPATIBLE_DESTINATION.
What channel variables does the Lua driver set?
| Variable | Values | Meaning |
|---|---|---|
| amdy_result | HUMAN | MACHINE | FAS | TIMEOUT | ERROR | The classification the dialplan branches on |
| amdy_cause | HUMAN | MACHINE | FAS | AUDIO_TIMEOUT | FORK_START_FAILED | SERVER_ERROR | CONNECTION_REFUSED | CONNECTION_LOST | Short marker or error label |
| amdy_stats | Raw server response body | Full response text from AMDY, if one arrived |
What are the common problems and their fixes?
| Symptom | Likely cause |
|---|---|
| No result, call ends after ~10s | Outbound TCP to api.amdy.io:2700 blocked. Check firewall rules. |
| Connection rejected right after opening | This host's IP is not allowlisted. Re-run the installer registration. |
| TIMEOUT on every call | Fork not running, or no read-direction audio at 8 kHz. Increase amdy_max_wait_ms if the greeting is long. |
| ERROR with FORK_START_FAILED | mod_audio_fork not loaded. Run: fs_cli -x "load mod_audio_fork" |
| Machines reaching agents | Routing matches too broadly. Bridge only on HUMAN. |
| Every call logs VID as Unknown | Caller ID name not set before the fork starts. |
| module_exists returns false | Module not loaded. Re-run the installer, check modules.conf.xml. |
Frequently asked questions
Does the integration support TLS (wss://)?
All verified source uses plain ws:// on port 2700. No wss:// endpoint is documented in the installer or client code. Contact us for integration access if your deployment requires TLS.
Why is there no X-API-Key on the WebSocket?
mod_audio_fork does not expose custom HTTP headers on the WebSocket upgrade request. AMDY authorizes fork connections by IP allowlist instead. The installer registers this host's public IP against your account using your API key over the portal REST API.
What audio format does AMDY expect?
8 kHz, 16-bit, mono, raw PCM (signed little-endian). The fork command uses mono 8k to match.
How long does AMDY wait for a result?
The Lua driver defaults to 10000 ms, clamped between 1000 ms and 30000 ms. Override it per call by setting amdy_max_wait_ms on the channel before running the Lua script.
Support
Need help? Email [email protected] with your FreeSWITCH version (fs_cli -x "version") and the output of the check script.
Not an AMDY customer yet? Sign up at app.amdy.io to get your API key and start detecting.