Hi everyone,
I’m trying to connect a browser-based agent to callers through an ARI bridge, but I’m stuck with no audio in either direction. I’d really appreciate any guidance.
Environment
-
-
Asterisk: 20.15.2 (built from source)
-
Frontend: React + SIP.js (WebRTC)
-
Signaling: WS → Asterisk (ws://127.0.0.1:8088/ws)
-
Bridge:
mixingbridge created via ARI -
Certificates auto-generated by Asterisk (
dtls_auto_generate_cert=yes)[transport-webrtc]
type=transport
protocol=wss
bind=0.0.0.0:8088[agent-auth]
type=auth
auth_type=userpass
username=agent-webrtc
password=StrongSecret[agent-webrtc]
type=endpoint
transport=transport-webrtc
context=agent
disallow=all
allow=opus,ulaw,alaw,g729,g722,gsm
dtmf_mode=rfc4733
trust_id_inbound=yes
send_connected_line=no
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
ice_support=yes
media_encryption=dtls
use_avpf=yes
rtcp_mux=yes
bundle=yes
dtls_verify=fingerprint
dtls_setup=actpass
dtls_auto_generate_cert=yes
webrtc=yes
use_ptime=yes
aors=agent-webrtc
auth=agent-auth
preferred_codec_only=no
timers=no
timers_sess_expires=1800
timers_min_se=90
identify_by=auth_username,username,ip[agent-webrtc]
type=aor
max_contacts=1
async function handleAgentJoinCall(callId, bridgeId, agentId, userId) {
try {
console.log(Agent ${agentId} joining call ${callId} on bridge ${bridgeId});const callInfo = await callDatabaseService.getCallFromDatabase(callId); if (!callInfo) throw new Error(`Call ${callId} not found`); const bridge = await clientRef.bridges.get({ bridgeId }); if (!bridge.channels || bridge.channels.length === 0) { throw new Error(`Call ${callId} ended – bridge has no channels`); } console.log(`✅ Bridge ${bridgeId} has ${bridge.channels.length} channels`); const agentChannel = await clientRef.channels.originate({ endpoint: `PJSIP/agent-webrtc`, app: ARI_CONFIG.appName, appArgs: `${callId},${bridgeId},${agentId}`, callerId: `Agent-${agentId}`, timeout: 30, variables: { CALL_ID: callId, BRIDGE_ID: bridgeId, AGENT_ID: agentId, USER_ID: userId, }, }); console.log(`✅ Agent channel created: ${agentChannel.id}`); await callDatabaseService.updateCallStatus(callId, { metadata: { ...callInfo.metadata, agentJoined: true, agentId, agentChannelId: agentChannel.id, agentJoinTime: new Date().toISOString(), waitingForAgent: false, }, }); return { success: true, agentChannelId: agentChannel.id };} catch (err) {
console.error(“
Error joining agent:”, err);
throw err;
}
}function registerEventHandlers(client) {
client.on(“StasisStart”, async (event, channel) => {
const context = event.channel?.dialplan?.context;
const [callIdArg, bridgeIdArg] = event.args || ;if (context !== "agent") { if (channel.state !== "Up") { await handleIncomingCall(event, channel, client); } else { await handleCallEnd(event); } return; } const callId = callIdArg || channel.variables?.CALL_ID; const bridgeId = bridgeIdArg || channel.variables?.BRIDGE_ID; console.log(`🌉 Agent channel ${channel.id} created for call ${callId}, bridge ${bridgeId}`); await client.channels.answer({ channelId: channel.id }); console.log(`✅ Agent channel ${channel.id} answered`); await client.bridges.addChannel({ bridgeId, channel: channel.id }); console.log(`✅ Agent channel ${channel.id} added to bridge ${bridgeId}`); await stopMOH(callId); console.log(`🔈 MOH stopped for call ${callId}`);});
client.on(“ChannelDtmfReceived”, async (event, channel) => {
const callInfo = await callDatabaseService.getCallByChannelId(channel.id);
if (callInfo) {
await handleDTMF(channel, event.digit, callInfo);
}
});client.on(“PlaybackFinished”, (ev, playback) => {
if (playback.channel?.id) {
console.log(✅ Playback finished for channel ${playback.channel.id});
}
});client.on(“ChannelDestroyed”, async (event, channel) => {
const callInfo = await callDatabaseService.getCallByChannelId(channel.id);
if (callInfo?.metadata?.agentChannelId === channel.id) {
await callDatabaseService.updateCallStatus(callInfo.callId, {
metadata: {
…callInfo.metadata,
agentJoined: false,
agentId: null,
agentChannelId: null,
agentLeaveTime: new Date().toISOString(),
},
});
}
});
}What works
-
Agent registers via WebSocket successfully
-
INVITE/200 OK/ACK completes
-
ARI bridge is created and agent channel is added
What works
-
Agent registers via WebSocket successfully
-
INVITE/200 OK/ACK completes
-
ARI bridge is created and agent channel is added
Questions for the community
-
Do I need extra config for SRTP/ICE when bridging WebRTC ↔ PSTN/SIP?
-
Should
rtp set debug onshow incoming/outgoing packets (currently nothing)? -
Any issue with how I originate & answer the agent channel in ARI?
-
Is there a better pattern for adding a WebRTC endpoint to an ARI bridge?
Thanks for taking the time to look at this!

-
-
-
-