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

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();
}
});