Official TypeScript/JavaScript SDK for building WebRTC Voice & Video Communications applications on the Firetell Platform (CPaaS, Virtual PBX, Call Center, Voice AI, SIP Trunking).
🎮 Live Interactive Demo: https://developers.firetell.com/firetell-client-sdk/example/
- 📞 WebRTC Outbound & Inbound Calls — High-quality audio/video calls using Native WebSockets per call session.
- 🎵 Early Media & PSTN Ringback Audio (
call.sdp/ 183 Session Progress) — Full support for early audio playback so callers hear PSTN ringback tones or early IVR prompts before the callee answers. - 🌐 Native WebSockets & SSE — Lightweight, zero external dependencies (no Socket.IO or heavy WS wrappers).
- 🔒 Per-Call Security Tokens — Short-lived
call_tokenJWT authentication per WebRTC session. - 🎧 Call Center Supervision — Built-in
listen(silent monitor),whisper(coach agent), andbarge(3-way call) supervision modes. - 📥 Instant Incoming Call Alerts — Real-time ring notifications via SSE (
call.ring) and WebRTC offer payloads (call.offer). - ⏸️ In-Call Control — Re-INVITE based Hold/Unhold, client-side Mute/Unmute, and DTMF tones (SIP INFO).
- 👥 Real-time Agent Presence — Track team availability via Server-Sent Events (
agent.state). - 📦 Multi-Format Distribution — Distributed as ESM (
.mjs), CommonJS (.js), and Browser Bundle (.global.js).
npm install @firetell/firetell-client-sdk
# or
yarn add @firetell/firetell-client-sdk
# or
pnpm add @firetell/firetell-client-sdk<script src="https://cdn.jsdelivr.net/npm/@firetell/firetell-client-sdk/dist/index.global.js"></script>
<script>
const token = "YOUR_AGENT_JWT_TOKEN";
const domain = "your-workspace.firetell.app";
const client = new Firetell.FiretellClient(token, domain);
</script>import {
FiretellClient,
Call,
ECallState,
EClientEventName,
ECallEventName,
} from "@firetell/firetell-client-sdk";
// Initialize client with agent JWT and workspace domain/URL
const client = new FiretellClient(
"YOUR_AGENT_JWT_TOKEN",
"https://your-workspace.firetell.app"
);
// Wait for connection initialization
const session = await client.ready;
console.log(`Connected as Agent: ${session.username} (${session.workspace_id})`);// HTML audio elements for WebRTC streams
const remoteAudio = document.getElementById("remote-audio") as HTMLAudioElement;
// Create Call instance
const call = new Call(client, {
to: "+84901234567", // Extension, agent username, or phone number
from: "+842871000000", // Optional outbound Caller ID
isVideo: false,
});
// 1. Listen for remote audio stream (Works for Early Media & Answered states!)
call.on(ECallEventName.REMOTE_STREAM, (remoteStream) => {
console.log("Received Remote Stream (Early Media / Audio Answer):", remoteStream);
remoteAudio.srcObject = remoteStream;
remoteAudio.play().catch(console.error);
});
// 2. Listen to call state changes
call.on(ECallEventName.STATE, (payload) => {
console.log("Call State Changed:", payload.state, payload.reason);
// States: INITIATED -> TRYING -> RINGING -> ACTIVE (ANSWERED) -> ENDED
switch (payload.state) {
case ECallState.RINGING:
console.log("Ringing / Early Media established...");
break;
case ECallState.ACTIVE:
console.log("Call Connected & Active!");
break;
case ECallState.ENDED:
console.log("Call Ended.");
break;
}
});
// 3. Listen to Real-time Speech-to-Text Transcription & Dialogues
call.on(ECallEventName.TRANSCRIPTION_DIALOGUE, (dialogue) => {
console.log(`[${dialogue.speaker}]: ${dialogue.text} (final: ${dialogue.speech_final})`);
// Render real-time live chat bubble / speech subtitles...
});
call.on(ECallEventName.TRANSCRIPTION_COMPLETED, (summaryData) => {
console.log("Call Transcription Completed:", summaryData.full_text);
console.log("AI Summary:", summaryData.summary);
console.log("Sentiment:", summaryData.sentiment);
});
// 4. Listen to Call Recording Events
call.on(ECallEventName.RECORDING_STARTED, (event) => {
console.log("Call Recording Started:", event);
});
call.on(ECallEventName.RECORDING_COMPLETED, (event) => {
console.log("Call Recording Stopped:", event);
});
// Or listen to all recording events:
call.onRecording((event) => {
console.log(`Recording Event [${event.type}]:`, event.data);
});
// 5. Initiate the call
await call.start();// 1. Instant Ring Alert (Trigger Ringing Popup & Play Ringtone)
client.events.on("call.ring", (ringData) => {
console.log("🔔 Incoming Call Alert!", ringData.call_id);
console.log("Caller:", ringData.from?.name || ringData.from?.number);
console.log("Hotline:", ringData.to?.name || ringData.to?.number);
// Show incoming call modal UI & play ringtone...
});
// 2. Incoming Call WebRTC Offer Ready
client.events.on("call.offer", (incomingCall) => {
console.log("Call Object Ready:", incomingCall.callId);
// Bind Remote Stream
incomingCall.on(ECallEventName.REMOTE_STREAM, (stream) => {
remoteAudio.srcObject = stream;
remoteAudio.play();
});
// Example: Accept button click handler
document.getElementById("btn-accept")?.addEventListener("click", async () => {
await incomingCall.accept();
console.log("Call Answered!");
});
// Example: Reject button click handler
document.getElementById("btn-reject")?.addEventListener("click", async () => {
await incomingCall.reject();
});
});Once a call is active (call object), you can perform the following controls:
// Mute microphone (stops sending audio)
call.mute();
console.log("Microphone Muted:", call.isMuted);
// Unmute microphone
call.unmute();
console.log("Microphone Muted:", call.isMuted);
// Toggle mute state
const isMutedNow = call.toggleMute();// Hold call (sends SDP renegotiation to server)
await call.hold();
// Unhold call
await call.unhold();// Transfer to another agent by username
await call.transfer("agent.jane");
// Transfer to an extension number
await call.transfer("100");
// Transfer to a team
await call.transfer("te_support_team_id");
// Transfer to a SIP account
await call.transfer("si_sip_account_id");
// Transfer with a reason
await call.transfer("agent.jane", "Customer needs billing support");// Send DTMF keypad digit ('0'-'9', '*', '#') via SIP INFO
await call.sendDTMF("1");// Hangup / Terminate Call
await call.hangup();Supervisors and team leaders can monitor ongoing active calls in 3 supervision modes:
// Mode 1: "listen" — Silent Monitoring (Supervisor hears both agent & customer)
const call = await client.startSupervision("cl_123456789", "listen");
// Mode 2: "whisper" — Whisper / Coach (Only the agent hears the supervisor)
const call = await client.startSupervision("cl_123456789", "whisper");
// Mode 3: "barge" — 3-Way Barge-In (Both agent & customer hear supervisor)
const call = await client.startSupervision("cl_123456789", "barge");
// Bind remote stream to audio element
call.on(ECallEventName.REMOTE_STREAM, (stream) => {
remoteAudio.srcObject = stream;
remoteAudio.play().catch(console.error);
});
// Stop supervision session
await call.hangup();Advanced Usage: You can also use
client.superviseCall(callId, mode)to fetch session credentials ({ call_token, ws_url }) and initialize the session manually viacall.joinSession(res.ws_url, res.call_token).
Track team presence and status changes in real-time via Server-Sent Events (SSE):
// Listen for agent status changes
client.events.on("agent.state", ({ username, state }) => {
// States: "online" | "available" | "incall" | "busy" | "offline"
console.log(`Agent ${username} is now ${state}`);
});
// Listen for forced state changes by supervisors
client.events.on("agent.state.forced", ({ target_username, new_state, forced_by, reason }) => {
console.log(`Agent ${target_username} was forced ${new_state} by ${forced_by}: ${reason}`);
});Agent Presence States:
| State | Description |
|---|---|
online |
Agent is actively connected via SSE event stream |
available |
Agent is reachable via VoIP push (has registered device) but not actively streaming SSE |
incall |
Agent is in an active call |
busy |
Agent is busy (set manually by agent — Do Not Disturb) |
offline |
Agent has no active connections or registered push devices |
// Set "Do Not Disturb" — agent won't receive incoming calls
await client.setPresence("busy");
// Return to active state (system determines: "online" or "available")
await client.setPresence("ready");In addition to WebRTC signaling, the SDK emits real-time call lifecycle state events over the persistent SSE stream for live dashboard monitoring, active call logs, and call telemetry:
// 1. New Call Session Created in Workspace
client.events.on("call.created", ({ data }) => {
console.log("Call Created:", data.call_id, data.direction, data.number);
});
// 2. Call Ringing / Started
client.events.on("call.started", ({ data }) => {
console.log("Call Ringing:", data.call_id, "From:", data.from?.number, "To:", data.to?.number);
});
// 3. Call Answered & Bridge Established
client.events.on("call.answered", ({ data }) => {
console.log("Call Answered:", data.call_id, "Answered at:", data.answer_time);
});
// 4. Call Ended & Final Metrics Available
client.events.on("call.ended", ({ data }) => {
console.log("Call Ended:", data.call_id, "Duration:", data.duration, "Cause:", data.hangup_cause);
});| Event Name | Payload Type | Description |
|---|---|---|
call.ring |
ICallRingParams |
Triggered instantly when an incoming call starts ringing (popup alert) |
call.offer |
Call |
Triggered when the WebRTC call object is ready to answer |
call.created |
{ event, workspace_id, data, timestamp } |
Real-time event when a new call is initialized |
call.started |
{ event, workspace_id, data, timestamp } |
Real-time event when a call begins ringing/progressing |
call.answered |
{ event, workspace_id, data, timestamp } |
Real-time event when a call is answered |
call.ended |
{ event, workspace_id, data, timestamp } |
Real-time event when a call finishes (includes duration, hangup_cause, cost) |
call.canceled |
{ event, workspace_id, data, timestamp } |
Triggered when ringing is canceled (call answered elsewhere or timed out) |
connection.state |
'connected' | 'connecting' | 'disconnected' |
Real-time connection status updates (handles background drops and reconnects without logging out) |
agent.state |
{ username, state } |
Real-time presence updates (online, available, incall, busy, offline) |
agent.state.forced |
{ target_username, new_state, forced_by, reason } |
Fired when a supervisor forces an agent's state to offline |
agent.updated |
Agent |
Fired when an agent profile (name, avatar, email) is updated |
agent.created |
Agent |
Fired when a new agent account is created |
agent.deleted |
{ id, username } |
Fired when an agent account is deleted |
team.created |
Team |
Fired when a workspace team is created |
team.updated |
Team |
Fired when team details/name are updated |
team.deleted |
{ id } |
Fired when a team is deleted |
team.assigned |
{ team_id, agent_id } |
Fired when an agent joins a team |
team.unassigned |
{ team_id, agent_id } |
Fired when an agent leaves a team |
contact.created |
Contact |
Fired when a contact is created |
contact.updated |
Contact |
Fired when contact info is updated |
contact.deleted |
{ id } |
Fired when a contact is deleted |
call.recording.ready |
ICallRecordingReadyEvent |
Fired on workspace stream when call recording file is fully processed and ready to play/download |
error |
{ code, message } |
General client errors or authentication failures |
| Event Name | Payload Type | Description |
|---|---|---|
state |
ISIPCallState |
Call state changes (INITIATED, TRYING, RINGING, ACTIVE, ONHOLD, ENDED) |
remoteStream |
MediaStream |
Fired when remote audio/video stream is available (including Early Media / Ringback) |
localStream |
MediaStream |
Fired when local microphone/camera stream is captured |
mediaState |
RTCIceConnectionState |
WebRTC ICE connection status updates (connecting, connected, failed) |
mute |
{ muted: boolean } |
Fired when local microphone is muted or unmuted |
transcription |
TranscriptionEvent |
Consolidated real-time speech transcription lifecycle events |
transcription.started |
ITranscriptionStartedEvent |
Fired when real-time speech-to-text session begins |
transcription.dialogue |
ITranscriptionDialogueEvent |
Fired on each live speech dialogue chunk / subtitle |
transcription.completed |
ITranscriptionCompletedEvent |
Fired when full call transcription, summary, and sentiment analysis finish |
recording |
CallRecordingEvent |
Consolidated call recording lifecycle events |
recording.started |
ICallRecordingStartedEvent |
Fired when audio recording starts |
recording.completed |
ICallRecordingCompletedEvent |
Fired when audio recording finishes |
This SDK is released under the MIT License.