Documentation Index Fetch the complete documentation index at: https://docs.blackbox.dasha.ai/llms.txt
Use this file to discover all available pages before exploring further.
Build browser-based voice calls with WebRTC. Handle WebSocket signaling, SDP exchange, and audio streams — with complete code for the full call lifecycle.
What you’ll learn: WebRTC setup, SDP offer/answer exchange, audio handling, and call termination.
Testing requires a valid API key. Generate a web integration token from your agent’s Web Integrations settings in the Dasha BlackBox dashboard before starting.
Prerequisites
Before you start, ensure you have:
A Dasha BlackBox agent with voice enabled
A web integration token (from Dashboard → Agent → Web Integrations)
The AllowWebCall feature enabled on your web integration
HTTPS environment (WebRTC requires secure context)
User permission for microphone access
Voice calls use WebRTC for real-time audio and WebSocket for signaling:
Step 1: Establish WebSocket connection
Connect and initialize with callType: "webCall":
Example: WebSocket connection for voice call
const token = 'YOUR_WEB_INTEGRATION_TOKEN' ;
const ws = new WebSocket (
`wss://blackbox.dasha.ai/api/v1/ws/webCall?token=${ token }`
);
ws. onopen = () => {
ws. send ( JSON . stringify ({
type: 'initialize' ,
timestamp: new Date (). toISOString (),
request: {
callType: 'webCall' ,
additionalData: {
userId: 'user-123'
}
}
}));
};
Step 2: Handle SDP offer
After initialization, the server sends an sdpInvite message with the WebRTC offer:
Example: SDP invite message format
SdpInviteMessage schema
Field Type Description type"sdpInvite"Message type identifier timestampstring (ISO 8601)Message timestamp data.sdpOfferstringWebRTC SDP offer
Step 3: Create peer connection and send answer
Create an RTCPeerConnection, set the remote offer, get microphone access, and send the answer:
Example: Handle SDP offer and create peer connection
let peerConnection = null ;
let localStream = null ;
ws. onmessage = async ( event ) => {
const message = JSON . parse (event.data);
switch (message.type) {
case 'event' :
if (message.name === 'connection' ) {
console. log ( 'Agent session ready, waiting for SDP offer...' );
}
break ;
case 'sdpInvite' :
await handleSdpInvite (message.data.sdpOffer);
break ;
case 'text' :
if (message.content.source === 'assistant' ) {
console. log ( 'Agent:' , message.content.text);
}
break ;
case 'conversationResult' :
console. log ( 'Call ended:' , message.result);
break ;
case 'error' :
console. error ( 'Error:' , message.data?.message);
break ;
}
};
async function handleSdpInvite ( sdpOffer ) {
// Create peer connection with STUN servers for NAT traversal
peerConnection = new RTCPeerConnection ({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
});
// Set remote description (server's offer)
await peerConnection. setRemoteDescription ({
type: 'offer' ,
sdp: sdpOffer
});
// Get microphone access
localStream = await navigator.mediaDevices. getUserMedia ({
audio: {
echoCancellation: true ,
noiseSuppression: true ,
autoGainControl: true
},
video: false
});
// Add audio track to peer connection
localStream. getTracks (). forEach ( track => {
peerConnection. addTrack (track, localStream);
});
// Create and set local description (answer)
const answer = await peerConnection. createAnswer ();
await peerConnection. setLocalDescription (answer);
// Send SDP answer to server
ws. send ( JSON . stringify ({
type: 'sdpAnswer' ,
timestamp: new Date (). toISOString (),
data: {
sdpAnswer: answer.sdp
}
}));
}
SdpAnswerMessage schema
Field Type Required Description type"sdpAnswer"Yes Message type identifier timestampstring (ISO 8601)Yes Message timestamp data.sdpAnswerstringYes WebRTC SDP answer
Step 4: Handle connection state changes
Monitor WebRTC connection state and handle audio:
Example: WebRTC connection state handlers
function setupPeerConnectionHandlers () {
// Handle connection state changes
peerConnection. onconnectionstatechange = () => {
console. log ( 'Connection state:' , peerConnection.connectionState);
switch (peerConnection.connectionState) {
case 'connected' :
console. log ( 'Voice call connected' );
updateUI ( 'connected' );
break ;
case 'disconnected' :
console. log ( 'Voice call disconnected' );
updateUI ( 'disconnected' );
break ;
case 'failed' :
console. error ( 'Voice call failed' );
handleConnectionFailure ();
break ;
}
};
// Handle ICE connection state
peerConnection. oniceconnectionstatechange = () => {
console. log ( 'ICE state:' , peerConnection.iceConnectionState);
if (peerConnection.iceConnectionState === 'failed' ) {
// Attempt ICE restart
peerConnection. restartIce ();
}
};
// Handle incoming audio from agent
peerConnection. ontrack = ( event ) => {
const audioElement = document. getElementById ( 'agent-audio' );
if (audioElement) {
audioElement.srcObject = event.streams[ 0 ];
}
};
// Handle ICE candidates
peerConnection. onicecandidate = ( event ) => {
if (event.candidate) {
// ICE candidates are included in the SDP, no action needed
console. log ( 'ICE candidate gathered' );
}
};
}
Step 5: End the call
Clean up resources when ending the call:
Example: End call and cleanup resources
function endCall () {
// Send terminate to server
if (ws && ws.readyState === WebSocket. OPEN ) {
ws. send ( JSON . stringify ({
type: 'terminate' ,
timestamp: new Date (). toISOString ()
}));
}
// Stop local audio tracks
if (localStream) {
localStream. getTracks (). forEach ( track => track. stop ());
localStream = null ;
}
// Close peer connection
if (peerConnection) {
peerConnection. close ();
peerConnection = null ;
}
}
// Handle page unload
window. addEventListener ( 'beforeunload' , endCall);
Next steps
Message Reference Complete schema documentation for all WebSocket messages
Error Handling Handle connection errors and edge cases
Chat Implementation Build text chat interfaces
Tool Execution Handle agent tool calls via WebSocket