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
| Field | Type | Required | Description |
|---|---|---|---|
to | string | ✅ | Recipient phone number or extension (e.g. +84901234567) |
from | string | ❌ | Outbound Caller ID phone number (e.g. +842471000000) |
type | string | ❌ | Call 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.
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
}
}
}
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
- JWT Verification: The client-api JWT token must contain a valid
call_flow_idclaim in its payload. - Call Initiation: The client makes a
POST /call-center/callsrequest. The API validates that thecall_flow_idexists in the workspace and hastrigger_typeset to'outbound'. - Internal VoIP Route: Direct PSTN dialing is disabled for the
client-apiaudience. The call behaves as an internal WebRTC VoIP call. - 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).
Auto-Hangup on Disconnect
Closing or dropping the WebSocket connection automatically ends active calls associated with that call_id.