I am trying to build a real-time voice assistant using Asterisk 22 and the Asterisk REST Interface (ARI). My goal is to stream live call audio to a Python WebSocket server for speech recognition using the externalMedia feature.
The Problem:
My Python ARI application successfully sends the POST /ari/channels/externalMedia command. The Asterisk REST API returns a “200 OK” status, and the Python logs show that the bridge and media channel are created successfully. However, Asterisk never attempts to connect to my WebSocket server running on ws://127.0.0.1:8089. The handler in my Python script which should be triggered by a new WebSocket connection is never called.
My Environment:
- Asterisk Version: 22.4.1
- OS: Ubuntu
- ARI Client: Python with
requestsandwebsocketslibraries.
What I Have Verified:
- WebSocket Server is Working: I have written a separate, simple Python WebSocket client and successfully connected to the server on port 8089 while my main ARI application is running. This proves the server is running correctly.
- No Firewall Issue: The successful connection test from a separate client on the same machine confirms that there is no local firewall (like
ufw) blocking port 8089. - Clean Bridge/Channel Logic: My Python code first creates a mixing bridge, adds the main call channel, and then creates the
externalMediachannel while specifying thebridgeIdduring creation. The Asterisk logs show all these steps completing without any errors.
The Core Question:
Given that all ARI commands succeed and there are no network/firewall issues, what could be preventing Asterisk from physically initiating the WebSocket client connection to the external_host? Is there a known issue, a missing module (format_slin, etc.), or a subtle PJSIP configuration that could cause this “silent failure” in Asterisk 22?
Below are my cleaned-up configuration files and the final “successful-looking but not working” Asterisk log. Any help or ideas would be greatly appreciated.
def make_call(self, number):
data = {
"endpoint": f"PJSIP/{number}@netgsm_trunk",
"context": "outbound-netgsm",
"extension": number,
"priority": 1,
"callerId": "xxxxxx",
"timeout": 30,
"app": "netgsm-realtime",
"appArgs": number
}
try:
response = self.session.post(f"{ARI_URL}/channels", json=data)
if response.status_code == 200:
channel_info = response.json()
channel_id = channel_info.get('id')
return channel_id
else:
print(f"❌ Call Error: {response.status_code} - {response.text}")
return None
except Exception as e:
print(f"❌ Call Error: {e}")
return None
def start_speech_recognition(self, channel_id):
print(f"🎙️ Starting speech recognition for {channel_id}")
try:
bridge_response = self.session.post(f"{ARI_URL}/bridges", json={"type": "mixing"})
bridge_response.raise_for_status()
bridge_id = bridge_response.json().get('id')
print(f"✅ Bridge created: {bridge_id}")
self.session.post(f"{ARI_URL}/bridges/{bridge_id}/addChannel", json={"channel": channel_id})
print(f"✅ Main channel ({channel_id}) added to bridge.")
external_host = "127.0.0.1:8089"
external_media_response = self.session.post(
f"{ARI_URL}/channels/externalMedia",
json={
"app": "netgsm-realtime",
"bridgeId": bridge_id,
"external_host": external_host,
"format": "slin"
}
)
external_media_response.raise_for_status()
external_channel_id = external_media_response.json().get('id')
print(f"✅ External Media channel created ({external_channel_id}) and added to bridge.")
self.active_calls[channel_id] = {
'start_time': datetime.now(),
'is_recognizing': True,
'transcript_file': f"/tmp/call_transcript_{channel_id}.txt",
'external_channel_id': external_channel_id,
'bridge_id': bridge_id
}
print(f"✅ Speech recognition started successfully: {channel_id}")
except requests.exceptions.HTTPError as e:
print(f"❌ HTTP Error (start_speech_recognition): {e.response.status_code} - {e.response.text}")
except Exception as e:
print(f"❌ General error while starting speech recognition: {e}")
pjsip.conf
[transport-ws]
type=transport
protocol=ws
bind=0.0.0.0:8088
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
[netgsm_trunk_auth]
type=auth
auth_type=userpass
username=xxxxxx
password=xxxxxx
realm=sip.netgsm.com.tr
[netgsm_trunk_aor]
type=aor
contact=sip:xxxxxx@sip.netgsm.com.tr:5060
qualify_frequency=60
[netgsm_trunk]
type=endpoint
context=outbound-netgsm
disallow=all
allow=ulaw
allow=alaw
allow=slin
aors=netgsm_trunk_aor
auth=netgsm_trunk_auth
outbound_auth=netgsm_trunk_auth
force_rport=yes
rewrite_contact=yes
transport=transport-udp
from_user=xxxxxxxx
from_domain=sip.netgsm.com.tr
[netgsm_registration]
type=registration
transport=transport-udp
outbound_auth=netgsm_trunk_auth
server_uri=sip:sip.netgsm.com.tr:5060
client_uri=sip:xxxxxx@sip.netgsm.com.tr
contact_user=xxxxxxxx
expiration=3600
retry_interval=60
max_retries=10
extentions.conf (for outbound calls)
[outbound-netgsm]
exten => _X.,1,NoOp(Giden Arama: ${CALLERID(num)} -> ${EXTEN})
exten => _X.,n,Set(CHANNEL(language)=tr) ;
exten => _X.,n,Dial(PJSIP/${EXTEN}@netgsm-trunk,30,tTr) ;
exten => _X.,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?answered:notanswered) ;
exten => _X.,n(answered),Stasis(netgsm-realtime,${EXTEN}) ;
exten => _X.,n,Hangup()
exten => _X.,n(notanswered),NoOp(Arama yanıtlanmadı.)
exten => _X.,n,Hangup()
Asterisk CLI output once calling is ongoing:
tahsilist-virtual-machine*CLI> core set debug 5
Core debug is still 5.
tahsilist-virtual-machine*CLI> core set verbose 5
Console verbose was 3 and is now 5.
-- Called 5345164540@netgsm_trunk
> 0x7f404c2f0710 -- Strict RTP learning after remote address set to: 185.88.7.206:11866
-- PJSIP/netgsm_trunk-00000001 is making progress
-- PJSIP/netgsm_trunk-00000001 answered
> Launching Stasis(netgsm-realtime,5345164540) on PJSIP/netgsm_trunk-00000001
-- <PJSIP/netgsm_trunk-00000001> Playing 'tr/test-voice.slin' (language 'en')
> 0x7f404c2f0710 -- Strict RTP switching to RTP target address 185.88.7.206:11866 as source
> 0x7f404c2f0710 -- Strict RTP learning complete - Locking on source address 185.88.7.206:11866
-- Channel PJSIP/netgsm_trunk-00000001 joined 'simple_bridge' stasis-bridge <84249a77-76a3-4c15-ade0-20298fef21ce>
> 0x7f3f78014040 -- Strict RTP learning after remote address set to: 127.0.0.1:8089
-- Called 127.0.0.1:8089/c(slin)
-- UnicastRTP/127.0.0.1:8089-0x7f3f780130b0 answered
> Launching Stasis(netgsm-realtime) on UnicastRTP/127.0.0.1:8089-0x7f3f780130b0
-- Channel PJSIP/netgsm_trunk-00000001 left 'simple_bridge' stasis-bridge <84249a77-76a3-4c15-ade0-20298fef21ce>