Why FreeSWITCH answering machine detection works differently
If you came to FreeSWITCH from Asterisk or VICIdial, you probably expected an AMD() application you could drop into the dialplan. FreeSWITCH does not have one. Its design is modular: the core handles calls, and features like detection come from modules you load.
That leaves a gap for outbound teams. A predictive or progressive dialer built on FreeSWITCH needs to know, within a couple of seconds of answer, whether a live person picked up. If it guesses wrong in one direction, agents spend their day listening to voicemail greetings. If it guesses wrong in the other, live people hear silence and a hang-up. And if it takes too long to guess, everyone hears dead air.
So the first decision is not how to tune AMD on FreeSWITCH. It is which kind of detection to use at all.
Your options for AMD on FreeSWITCH
There are four realistic approaches. Each answers a slightly different question, which is where most confusion comes from.
1. No AMD at all
Every answered call goes to an agent, and the agent dispositions voicemail by hand. This is simple and never hangs up on a person. The cost is agent time: every greeting, every carrier message and every screening assistant lands on a paid seat. For low-volume or high-value campaigns that can be acceptable. For predictive dialing it usually is not.
2. mod_avmd (beep detection)
mod_avmd ships with FreeSWITCH and detects the beep a voicemail system plays after its greeting. It is good at that job. But a beep only arrives after the whole greeting has played, which can be many seconds into the call, and not every voicemail system plays a clean, single tone. More importantly, it does not tell you who answered. It cannot hear the difference between a person saying "Hello?" and a recording saying "Hi, you've reached..." We cover this in detail in mod_avmd vs AI AMD.
3. Carrier or CPaaS AMD
Some voice platforms offer detection as a paid add-on. The catch is that it usually only applies to calls you place through their API. Telnyx, for example, offers AMD on calls controlled through its Call Control API, not on calls your own switch places over a SIP trunk. Using it typically means moving call control off FreeSWITCH, which is a much bigger change than adding detection. See our Telnyx AMD comparison and Twilio AMD comparison for pricing and limits.
4. AI AMD over a WebSocket
The call stays on your FreeSWITCH. The first seconds of answered audio are streamed to a detection service, which sends back one verdict. This is how nomoVM works. It hears what the answer sounds like rather than counting silences, so a short "Hello?" and a long greeting are judged on their content.
| Option | Tells you who answered? | Stays on your FreeSWITCH? | Typical use |
|---|---|---|---|
| No AMD | Agent decides | Yes | Low volume, high value calls |
| mod_avmd | No, only finds the beep | Yes | Timing voicemail drops |
| Carrier / CPaaS AMD | Yes | Usually no, calls must go through their API | Teams already dialing through that API |
| nomoVM AI AMD | Yes: HUMAN, MACHINE or FALLBACK | Yes | Outbound dialers that want to keep their stack |
How nomoVM works on FreeSWITCH
The integration has three moving parts, and only one of them is new code on your server.
- mod_audio_stream, an open-source FreeSWITCH module that streams call audio to a WebSocket. It is a separate project (AGPL-3.0 with a FreeSWITCH linking exception for the community edition), so check it suits how you run FreeSWITCH.
- nomovm_amd.lua, a short script run by
mod_lua, which ships with FreeSWITCH. It starts the stream, waits for the verdict and sets channel variables. - The nomoVM service at
wss://amd.nomovm.com/. Your key travels in anAuthorization: Bearerheader over TLS, never in the URL.
When the callee answers, your dialplan runs the script. The caller's audio (8 kHz, mono) streams to nomoVM. About 2 seconds later, or up to 3.2 seconds for unclear calls, the service returns a verdict. The script sets AMDSTATUS to HUMAN, MACHINE or FALLBACK, sets AMDCAUSE to the reason, stops the stream and hands control back to the dialplan.
AMDCAUSE can be HUMAN, MACHINE, SCREENING (Google Call Screen or iPhone call screening), SILENCE, TONE (fax, SIT, ringback, beeps), BUSY or UNKNOWN. AMDSTATUS is HUMAN only when the cause is HUMAN, so anything uncertain stays away from your agents.
Step-by-step setup
For a server that already places outbound calls, the whole setup takes about 10 minutes. No new servers, no new carrier, no change to your agents.
Load mod_audio_stream
Install the module as its README describes (it publishes packages for recent Debian releases with FreeSWITCH from SignalWire's repository), then load it and make it load at every start:
fs_cli -x "load mod_audio_stream" # autoload_configs/modules.conf.xml <load module="mod_audio_stream"/>Install the script
Put
nomovm_amd.luain your FreeSWITCH scripts folder. It has no dependencies beyondmod_lua.cd /usr/share/freeswitch/scripts # or /etc/freeswitch/scripts on some installs curl -fsSLO https://nomovm.com/assets/nomovm_amd.luaCreate a key
Sign up free and create a WS key. One key per FreeSWITCH server makes the dashboard easy to read, since each server's calls are listed under its own key.
Add the AMD extension
In
dialplan/default.xml, or the context your outbound calls land in:<extension name="nomovm_amd"> <condition field="destination_number" expression="^nomovm_amd$"> <action application="lua" data="nomovm_amd.lua wss://amd.nomovm.com/ YOUR_KEY 4500"/> <action application="transfer" data="amd_${AMDSTATUS} XML default"/> </condition> </extension>The last argument,
4500, is the client timeout in milliseconds. It covers the 2 to 3.2 second decision plus network time.Add one extension per verdict
<extension name="amd_human"> <condition field="destination_number" expression="^amd_HUMAN$"> <action application="transfer" data="agents XML default"/> </condition> </extension> <extension name="amd_machine"> <condition field="destination_number" expression="^amd_MACHINE$"> <action application="hangup"/> </condition> </extension> <extension name="amd_fallback"> <condition field="destination_number" expression="^amd_FALLBACK$"> <action application="transfer" data="agents XML default"/> </condition> </extension>Replace
agentswith your own queue or callcenter extension. Then reload:fs_cli -x "reloadxml".Point outbound calls at it
Have your dialer originate each call into the AMD extension, so detection starts at the moment of answer:
originate {origination_caller_id_number=15550001111}sofia/gateway/my_carrier/15551234567 nomovm_amd XML defaultDialers that drive FreeSWITCH through the event socket can send the same command with
bgapi originate. The extension only runs after the callee answers, which is exactly when AMD should start.
Routing on the verdict
The pattern amd_${AMDSTATUS} works because FreeSWITCH expands variables in action data when the action runs. By the time transfer executes, the script has set the variable, so the call goes to amd_HUMAN, amd_MACHINE or amd_FALLBACK.
Splitting by cause
If you want different handling per cause, transfer on AMDCAUSE instead, or add a second condition. Common choices:
- MACHINE: hang up, or play a voicemail message.
- SCREENING: some teams play a short identification so the person can pick up; others hang up. Decide per campaign.
- BUSY and TONE: hang up and let the dialer retry under its own rules.
- SILENCE: hang up; nobody is there to talk to.
The FALLBACK route
FALLBACK means nomoVM did not judge the call: a wrong key, a plan limit, the service unreachable, or mod_audio_stream not loaded. It is returned before any audio is used, so the call is not delayed and is never billed. Route it to whatever you did before, usually straight to agents. A timeout or error after streaming has started is different: that gives MACHINE with cause UNKNOWN, so only a confirmed person ever reaches an agent.
MACHINE, you can start mod_avmd to wait for the beep before playing your message. That way the message lands after the greeting instead of over it.Testing your setup
- Confirm the module is loaded:
fs_cli -x "uuid_audio_stream"should print its usage text, not "command not found". - Call your own mobile and let it ring through to voicemail. The FreeSWITCH log should show
MACHINE. - Call it again and answer with a normal "Hello?". You should see
HUMAN, and the call should land in your agent route. - Open the nomoVM dashboard. Each call is listed with its verdict, cause, timing and the audio the service heard. You can play it back or export the log as CSV.
If every call shows FALLBACK, check the key in the dialplan line, check that the module is loaded, and check that the server can reach amd.nomovm.com on port 443.
Once the basic test passes, run it on one real campaign before switching everything. Label a few hundred calls yourself and compare: how many live people reached agents, and how many agent calls were actually people. Those are the two numbers that matter, and nomoVM publishes both on its accuracy page.
FusionPBX and other FreeSWITCH-based systems
FusionPBX runs on FreeSWITCH, so the same approach works. Install and load mod_audio_stream on the server, copy the script into the scripts folder, then create the four dialplan entries from FusionPBX's dialplan manager instead of editing XML by hand. Keep the extension names the same so the amd_${AMDSTATUS} transfer resolves.
The same goes for custom dialers built on FreeSWITCH. If your platform can load a module and add an extension, it can use nomoVM. If you cannot use mod_audio_stream, anything that can stream 8 kHz, 16-bit audio to a WebSocket can talk to the WebSocket API directly.
Cost and next steps
nomoVM bills one call per verdict on an answered call, with no per-server or per-agent fees. The free plan covers 30,000 calls a month with no card, and it stops at the limit rather than billing you. Starter is $99 a month for 1,000,000 calls, Growth $199 for 3,000,000 and Scale $499 for 10,000,000. Full details are on the pricing page.
To recap the FreeSWITCH path:
- Load
mod_audio_streamand copynomovm_amd.luato your scripts folder. - Add the
nomovm_amdextension and three verdict extensions. - Originate outbound calls into
nomovm_amd. - Test with your own phone, then one campaign, then the rest.
Your carrier, your gateways and your agents stay exactly as they are. Create a free account and follow the FreeSWITCH integration page for the latest script and dialplan.
Frequently asked questions
Does FreeSWITCH have built-in answering machine detection?
No. FreeSWITCH has no AMD() application like Asterisk. The closest built-in tool is mod_avmd, which listens for the voicemail beep after a greeting but does not tell you whether a person or a machine answered.
What do I need to run nomoVM on FreeSWITCH?
The open-source module mod_audio_stream, loaded in FreeSWITCH, plus the nomovm_amd.lua script and a nomoVM key. mod_lua ships with FreeSWITCH. You do not need new servers, a new carrier or changes to your agents.
How long does nomoVM take to decide on a FreeSWITCH call?
Most calls get a verdict about 2 seconds after answer. Only unclear calls listen for up to 3.2 seconds. The script uses a 4500 ms timeout so it always returns in time for your dialplan to route the call.
What happens if nomoVM cannot be reached?
The script sets AMDSTATUS to FALLBACK before any audio is used, for example with a wrong key, a plan limit or no network. Your dialplan then routes the call the way you choose, such as straight to agents. Fallback calls are never billed.
Does this work on FusionPBX?
Yes, as long as you can load mod_audio_stream and add dialplan entries. FusionPBX lets you add dialplan entries from its admin screens, so the same extensions shown in this guide can be created there.
Can I still use mod_avmd?
Yes. mod_avmd is useful after nomoVM has said MACHINE, to wait for the voicemail beep before you play a message. The two tools answer different questions and work well together.