Skip to main content
🤖 LLM Friendly: This page is available in raw Markdown format for LLM consumption:calls.md|Get full documentation:llms.txt/llms-full.txt

Outbound Calls & WebRTC Signaling

Outbound calls are initiated via HTTP REST API (POST /call-center/calls). The API validates permissions and returns a short-lived call_token and ws_url.

The client then opens a native WebSocket connection for WebRTC SDP signaling scoped exclusively to that call.


Make Outbound Call

POST /call-center/calls

Request Body

FieldTypeRequiredDescription
tostringRecipient phone number or extension (e.g. +84901234567)
fromstringOutbound Caller ID phone number (e.g. +842471000000)
typestringCall type: audio (default) or video

Request

curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls" \
-H "Authorization: Bearer YOUR_AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"to": "+84901234567",
"from": "+842471000000"
}'

Response 201 Created

{
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"status": "created",
"call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"ws_url": "wss://{workspace_id}.firetell.app/ws",
"expires_in": 900
}

Native WebSocket Signaling Protocol

Once call_token is received, open a native WebSocket to ws_url.

important

3-Second Authentication Requirement Upon opening the WebSocket connection, the client must send the session.connect event within 3 seconds, containing the call_token. Unauthenticated sockets are automatically terminated after 3 seconds.

1. Handshake (session.connect)

Client Send:

{
"event": "session.connect",
"data": {
"token": "YOUR_CALL_TOKEN"
}
}

Server Response:

{
"event": "session.connected",
"data": {
"session_id": "socket_uuid_123",
"workspace_id": "ws_123456789",
"username": "agent.john",
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"mode": "participant"
}
}

2. WebRTC SDP Offer (call.offer)

Server Dispatch for Incoming Call:

{
"event": "call.offer",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"from": "+84901234567",
"from_name": "Nguyen Van A (VIP)",
"to": "+842471000000",
"sdp": {
"type": "offer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
},
"is_transfer": false
}
}

Client Send for Outbound Call:

{
"event": "call.offer",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": {
"type": "offer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}
}

Server Response:

{
"event": "call.offered",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1"
}
}

3. ICE Candidates (call.candidate)

Client Send:

{
"event": "call.candidate",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"candidate": {
"candidate": "candidate:1 1 UDP 2013266431 192.168.1.1 54321 typ host",
"sdpMid": "0",
"sdpMLineIndex": 0
}
}
}
info

ICE Candidate Signaling Note
Firetell Voice Platform uses Full SDP Exchange (Non-Trickle ICE) where ICE candidates (a=candidate:...) are bundled directly within the initial SDP offer (call.offer) and SDP answer (call.answer). Sending individual call.candidate events via WebSocket is supported for compatibility (acknowledged with call.candidate_ack), but bundling candidates directly in the SDP is the recommended and primary mechanism for media establishment.


4. End Call (call.hangup / call.ended)

Client Send (call.hangup):

To hang up the call:

{
"event": "call.hangup",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1"
}
}

Server Dispatch (call.ended):

Notifies the client that the call session has been terminated.

{
"event": "call.ended",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"reason": "hangup"
}
}

5. Answer Call (call.answer / call.answered)

Client Send (call.answer):

Sent by the client to accept an incoming call, containing the client's WebRTC SDP answer.

{
"event": "call.answer",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}

Server Dispatch (call.answered):

Notifies the client (e.g. for an outbound call) that the call has been answered.

{
"event": "call.answered",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}

6. Reject Call (call.reject / call.rejected)

Client Send (call.reject):

Sent by the client to reject an incoming call.

{
"event": "call.reject",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1"
}
}

Server Dispatch (call.rejected):

Notifies the client that the call was rejected.

{
"event": "call.rejected",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"reason": "rejected"
}
}

7. Hold Call (call.hold / call.held)

Client Send (call.hold):

Sent by the client to place the active call on hold.

{
"event": "call.hold",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": {
"type": "offer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}
}

Server Dispatch (call.held):

Notifies the client that the call was successfully held.

{
"event": "call.held",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": {
"type": "answer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}
}

8. Resume Call (call.unhold / call.unheld)

Client Send (call.unhold):

Sent by the client to take the call off hold.

{
"event": "call.unhold",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": {
"type": "offer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}
}

Server Dispatch (call.unheld):

Notifies the client that the call was successfully resumed.

{
"event": "call.unheld",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"sdp": {
"type": "answer",
"sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..."
}
}
}

9. Mute Call (call.mute)

Client Send:

Sent by the client to update the local microphone mute state.

{
"event": "call.mute",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"muted": true
}
}

10. Send DTMF (call.dtmf)

Client Send:

Sent by the client to play DTMF tones on the call.

{
"event": "call.dtmf",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"digit": "5",
"duration": 250
}
}

11. Transfer Call (call.transfer / call.transferred)

Client Send (call.transfer):

Sent by the client to transfer the call to another agent.

{
"event": "call.transfer",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"to": "agent.smith",
"team_id": "te_123456"
}
}

Server Dispatch (call.transferred):

Notifies the client that the call transfer is complete.

{
"event": "call.transferred",
"data": {
"call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1",
"reason": "transferred"
}
}

Client-Initiated Call Flows (client-api)

When integrating calling capabilities into end-user client applications (e.g., driver apps, customer support widgets) using client-api JWT tokens, calls are routed through a Visual Call Flow builder rather than being connected directly as normal operator/agent calls.

Workflow & Routing

  1. JWT Verification: The client-api JWT token must contain a valid call_flow_id claim in its payload.
  2. Call Initiation: The client makes a POST /call-center/calls request. The API validates that the call_flow_id exists in the workspace and has trigger_type set to 'outbound'.
  3. Internal VoIP Route: Direct PSTN dialing is disabled for the client-api audience. The call behaves as an internal WebRTC VoIP call.
  4. Flow Execution: When the client connects via WebSocket and initiates the call, Firetell automatically routes the call directly into the compiled Call Flow actions (e.g., executing an IVR, connecting the caller to a team, or routing to a specific agent).

tip

Auto-Hangup on Disconnect Closing or dropping the WebSocket connection automatically ends active calls associated with that call_id.