---
sidebar_position: 3
title: Quickstart
description: A quickstart guide to making and receiving calls using the Firetell Client SDK.
---

# 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/](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.

```typescript
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).

```typescript
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.

```typescript
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()`:

```typescript
// 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

```typescript
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)

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