Asterisk over AudioSocket
This chapter connects Asterisk to the gateway with AudioSocket, the default telephony mode. The dialplan opens a TCP connection to the gateway for each call and streams 8 kHz audio both ways. You need Asterisk 16 or later with the AudioSocket application loaded, and a gateway that is already running (see Quick start).
To connect a PBX or carrier by SIP instead, see SIP trunk.
Dialplan
Add this to extensions.conf (or extensions_custom.conf on FreePBX):
[voice-assistant]
exten => _X.,1,Answer()
same => n,Set(SESSION_UUID=${TRIM(${SHELL(uuidgen)})})
same => n,AudioSocket(${SESSION_UUID},127.0.0.1:8080)
same => n,Hangup()
SESSION_UUID names the call on both sides. AudioSocket() requires an RFC 4122 UUID as its first argument and rejects ${UNIQUEID}, so the dialplan generates one. The gateway uses the same value to find the metadata you posted for that call. SHELL() needs the func_shell module and the uuidgen binary (package uuid-runtime on Debian and Ubuntu).
Where the call arrives over a SIP trunk that already carries X-Session-UUID (SIP trunk), prefer that header and fall back to a generated id:
same => n,Set(SESSION_UUID=${IF($["${PJSIP_HEADER(read,X-Session-UUID)}" != ""]?${PJSIP_HEADER(read,X-Session-UUID)}:${TRIM(${SHELL(uuidgen)})})})
The gateway listens for AudioSocket on these variables:
AUDIOSOCKET_HOST=127.0.0.1
AUDIOSOCKET_PORT=8080
HTTP_HOST=127.0.0.1
HTTP_PORT=8081
AUDIOSOCKET_HOST defaults to 127.0.0.1 and AUDIOSOCKET_PORT to 8080. HTTP_PORT also defaults to 8080; this manual sets it to 8081 (see Quick start) so the two listeners do not collide.
If Asterisk runs on another host, set AUDIOSOCKET_HOST=0.0.0.0 and HTTP_HOST=0.0.0.0, use the gateway's address in the AudioSocket() and metadata URLs, and allow both ports from the PBX only:
sudo ufw allow from <pbx-address> to any port 8080 proto tcp
sudo ufw allow from <pbx-address> to any port 8081 proto tcp
Also add the PBX to the HTTP whitelist, keeping loopback so the local health check still works:
HTTP_ALLOWED_IPS=127.0.0.1,::1,<pbx-address>
Do not publish either port to the internet.
Send caller details first
The gateway starts a call with defaults: unknown caller, the default language and the default bot. To change that, post the call's details to the HTTP port before AudioSocket(). The POST must finish first, so put it earlier in the dialplan.
[voice-assistant]
exten => _X.,1,Answer()
same => n,Set(SESSION_UUID=${TRIM(${SHELL(uuidgen)})})
same => n,Set(META=caller_id=${URIENCODE(${CALLERID(num)})}&caller_name=${URIENCODE(${CALLERID(name)})}&channel=${URIENCODE(${CHANNEL})}&botId=support&language_code=en-IN)
same => n,Set(CURL_RESULT=${CURL(http://127.0.0.1:8081/session/${SESSION_UUID}/metadata,${META})})
same => n,AudioSocket(${SESSION_UUID},127.0.0.1:8080)
same => n,Hangup()
Build the body in a variable and wrap caller-supplied values in URIENCODE(), so commas and ampersands in a caller name do not break the arguments. Asterisk's CURL() sends its second argument as a form-encoded POST body. The gateway accepts form and JSON bodies, so you can also post JSON from a shell, which is handy for testing:
curl -X POST http://127.0.0.1:8081/session/<uuid>/metadata \
-H 'Content-Type: application/json' \
-d '{"caller_id":"+15550100","botId":"support","language_code":"en-IN"}'
HTTP_ALLOWED_IPS is a whitelist of exact addresses or IPv4 CIDR ranges. Set it to 127.0.0.1,::1 as a minimum. When it is unset the whitelist is off and the port is protected only by the address it binds to.
| Key | What it does |
|---|---|
caller_id |
Caller number, passed to the bot and shown in logs |
caller_name |
Caller name |
destination_number |
The number that was dialled |
language_code |
Call language, for example ml-IN or en-IN |
channel |
The Asterisk channel name. Required for transfers: without it the transfer fails and the caller hears the transfer-failed message |
botId |
Which bot answers this call; an exact, case-sensitive id, and an unknown id falls back to the default bot |
tts_provider |
Speech provider for this call only |
tts_model |
Model for providers that take one, set with tts_provider |
tts_voice |
Voice for this call only |
realtimeProvider |
litebot, gemini-live, or off (also pipeline or empty) for the standard pipeline, for this call |
An unrecognised tts_provider is dropped with a warning in the log and the global provider is used, so a typo degrades quietly. Any other key you post is kept with the session.
One gateway, many bots
One gateway serves many numbers. Give each number its own context, or one context with a ${EXTEN} switch, and post a different bot, language and voice before AudioSocket():
[from-did]
exten => 5550101,1,Set(SESSION_UUID=${TRIM(${SHELL(uuidgen)})})
same => n,Set(META=caller_id=${URIENCODE(${CALLERID(num)})}&channel=${URIENCODE(${CHANNEL})}&destination_number=${EXTEN}&botId=sales&language_code=en-IN)
same => n,Set(CURL_RESULT=${CURL(http://127.0.0.1:8081/session/${SESSION_UUID}/metadata,${META})})
same => n,Goto(vexyl-connect,s,1)
exten => 5550102,1,Set(SESSION_UUID=${TRIM(${SHELL(uuidgen)})})
same => n,Set(META=caller_id=${URIENCODE(${CALLERID(num)})}&channel=${URIENCODE(${CHANNEL})}&destination_number=${EXTEN}&botId=support&language_code=ml-IN)
same => n,Set(CURL_RESULT=${CURL(http://127.0.0.1:8081/session/${SESSION_UUID}/metadata,${META})})
same => n,Goto(vexyl-connect,s,1)
[vexyl-connect]
exten => s,1,Answer()
same => n,AudioSocket(${SESSION_UUID},127.0.0.1:8080)
same => n,Hangup()
The two numbers above are placeholders. A bot is named by its id, the same id its definition carries; how bots are written is in AI brain.
To use a specific voice for one number, add tts_provider and tts_voice to the same POST. The voice id must belong to the provider you name, otherwise that provider rejects the request.
Co-hosting Asterisk and the gateway
When both run on one machine:
- Keep
AUDIOSOCKET_HOSTandHTTP_HOSTon127.0.0.1. Nothing outside the box needs them. - If you also run the SIP bridge (
TELEPHONY_MODE=both), the gateway's RTP range (RTP_PORT_MIN,RTP_PORT_MAX) must not overlap Asterisk'srtpstartandrtpendinrtp.conf. Both default to 10000-20000. An overlap makes the gateway lose ports to Asterisk. Move one range; details are in SIP trunk.
Plain AudioSocket calls do not use the gateway's RTP range, so this only matters when the SIP bridge is on.
Transfers
When a bot decides to hand a caller to a person, the gateway needs somewhere to send the request. On AudioSocket calls that is an HTTP endpoint you provide:
TRANSFER_ENABLED=true
TRANSFER_ENDPOINT=http://127.0.0.1/transfer.php
TRANSFER_TIMEOUT=5000
The gateway POSTs JSON to TRANSFER_ENDPOINT:
{
"channel": "PJSIP/100-00000001",
"callerid": "+15550100",
"uuid": "<session-uuid>",
"timestamp": "2026-01-01T10:00:00.000Z"
}
Reply with HTTP 200 once you have moved the channel; any other status counts as a failure, the caller hears the transfer-failed message and the call ends. Your endpoint does the move, for example with an AMI Redirect of channel into a queue context:
[agent-queue]
exten => 7000,1,Answer()
same => n,Queue(support-queue,t,,,300)
same => n,Hangup()
TRANSFER_SIP_URI is only for calls on the SIP bridge; it is not used for AudioSocket calls. The messages the caller hears and the timeouts are covered in Conversation behaviour.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| AudioSocket connects, then the call hangs up at once | The gateway is not running, is listening on another port, or AUDIOSOCKET_HOST is 127.0.0.1 and Asterisk is remote. On the gateway host run ss -ltn (look for 8080) to confirm the AudioSocket port is listening (/health on the HTTP port proves only the HTTP side) and compare it with the AudioSocket() line. |
| Call connects but there is no audio either way | The AudioSocket module is not loaded: asterisk -rx "module show like audiosocket" should list it as Running. Also check the call's codec is a plain telephony one such as ulaw or alaw. |
| Bot ignores the caller name, language or bot id | The POST ran after AudioSocket(), or the UUID differs. Use the same ${SESSION_UUID} in both and read it back with curl http://127.0.0.1:8081/session/<uuid>/metadata. A 403 appears only when HTTP_ALLOWED_IPS is set and lacks the PBX address; the gateway log shows Blocked request from <ip>. |
| Transfer does nothing | TRANSFER_ENABLED is not true, or TRANSFER_ENDPOINT is unset. With neither endpoint nor TRANSFER_SIP_URI, the gateway logs the request, plays the transfer-failed message and the call ends. |