Quickstart Guide
This guide walks you through the core workflows of the Firetell Client SDK: connecting, listening for SSE events, initiating outbound calls via REST, accepting incoming calls, and controlling active call states.
🎮 Try the Live Interactive Demo: https://developers.firetell.com/firetell-client-sdk/example/
Step 1: Initialize the Client​
To connect to the Firetell platform, create a new instance of FiretellClient. The constructor takes your agent's JWT token and the workspace domain.
Wait for the ready promise to resolve before performing actions.
import { FiretellClient } from "@firetell/firetell-client-sdk";
const token = "your-agent-jwt-token";
const domain = "yourcompany.firetell.app"; // Workspace API domain
const client = new FiretellClient(token, domain);
try {
// Wait for session initialization and SSE event stream setup
const session = await client.ready;
console.log("Successfully authenticated as:", session.username);
} catch (error) {
console.error("Initialization failed:", error);
}
Step 2: Listen to Realtime Events (SSE)​
Register event listeners on the client's events emitter to respond to incoming calls or teammate status updates via Server-Sent Events (SSE).
import { EClientEventName, ICallRingParams, Call } from "@firetell/firetell-client-sdk";
// Handle signaling and socket errors
client.events.on(EClientEventName.ERROR, (error) => {
console.error(`Signaling error [Code ${error.code}]:`, error.message);
});
// Track agent presence updates in the workspace via SSE
client.events.on(EClientEventName.AGENT_STATE, (payload) => {
const data = payload.data || payload;
console.log(`Agent ${data.username || data.id} is now ${data.state}`);
});
// Track agent lifecycle creation events via SSE
client.events.on(EClientEventName.AGENT_CREATED, (payload) => {
const data = payload.data || payload;
console.log(`New agent account created: ${data.display_name || data.username} (${data.id})`);
});
// 1. Instant Ring Alert via SSE (CALL_RING — Triggers Incoming Call Ringing UI)
client.events.on(EClientEventName.CALL_RING, (ringData: ICallRingParams) => {
console.log("Incoming call ring alert for call_id:", ringData.call_id);
console.log("Caller (from):", ringData.from?.name, ringData.from?.number); // "Nguyen Van A", "+84901234567"
console.log("Target (to):", ringData.to?.name, ringData.to?.number); // "Support Team", "+842471000000"
// -> Display Ringing Screen & Ringtone Popup immediately!
});
// 2. WebRTC Call Offer Ready (CALL_OFFER — WebRTC Call object ready for answer)
client.events.on(EClientEventName.CALL_OFFER, (call: Call) => {
console.log("Incoming WebRTC Call ready to answer:", call.callId);
console.log("Caller:", call.from_name || call.from);
});
Step 3: Make an Outbound Call​
To start a new call, instantiate a Call object, subscribe to state/stream events, and invoke start().
The SDK automatically calls POST /api/v1/call-center/calls to obtain a call_token, then opens a per-call native WebSocket for WebRTC signaling.
import { Call, ECallState } from "@firetell/firetell-client-sdk";
// Create a call configuration
const call = new Call(client, {
to: "+84901234567", // Destination number or internal extension
number: "84281234567", // Outbound Caller ID DID phone number
isVideo: false,
});
// 1. Monitor call lifecycle states
call.on("state", ({ call_id, state, reason }) => {
console.log(`Call state updated: ${state}`); // INITIATED → ANSWERED → ENDED
if (["ENDED", "CANCEL", "ERROR"].includes(state)) {
console.log(`Call terminated. Reason: ${reason || "Normal Hangup"}`);
}
});
// 2. Attach remote audio stream to HTML element
call.on("remoteStream", (stream) => {
if (stream) {
const remoteAudio = document.getElementById("remoteAudio") as HTMLAudioElement;
remoteAudio.srcObject = stream;
remoteAudio.play();
}
});
// 3. Initiate call
await call.start();
Step 4: Call Supervision (Supervisor Only)​
Supervisors can monitor, coach, or join active calls using superviseCall():
// Modes: "listen" (silent), "whisper" (coach), "barge" (3-way)
const supervision = await client.superviseCall("cl_123456789", "listen");
console.log("Supervision started:", supervision.status);
Step 5: Handling Concurrent Incoming Calls​
When an agent receives a 2nd call while already on an active call, choose one of two integration patterns:
Option A: Call Waiting & Hold​
let activeCall: Call | null = null;
client.events.on("call.offer", async (incomingCall: Call) => {
if (activeCall && activeCall.active) {
// Show Call Waiting UI notification for incomingCall
console.log(`Call Waiting from ${incomingCall.from} (${incomingCall.from_name})`);
// When Agent clicks "Hold & Answer":
await activeCall.hold();
await incomingCall.accept();
activeCall = incomingCall;
} else {
activeCall = incomingCall;
}
});
Option B: Auto-Reject (Busy Policy)​
client.events.on("call.offer", async (incomingCall: Call) => {
if (client.activeCalls.size > 1) {
// Reject 2nd call with Busy status (SIP 486) to trigger backend fallback branch
await incomingCall.reject();
}
});