Skip to main content

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":
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:
{
  "type": "sdpInvite",
  "timestamp": "2025-01-20T10:00:02Z",
  "data": {
    "sdpOffer": "v=0\r\no=- 1234567890 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n..."
  }
}

SdpInviteMessage schema

FieldTypeDescription
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:
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

FieldTypeRequiredDescription
type"sdpAnswer"YesMessage type identifier
timestampstring (ISO 8601)YesMessage timestamp
data.sdpAnswerstringYesWebRTC SDP answer

Step 4: Handle connection state changes

Monitor WebRTC connection state and handle audio:
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:
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