---
title: 'Supervise live calls: listen, whisper, barge'
description: >-
  Put a supervisor on a live call to listen, whisper to the agent or barge in,
  and switch modes through the API without anyone redialling.
summary: >-
  One supervisor per call, three modes, switched through the API. Start from
  your server with @sautikit/node, answer in the browser with @sautikit/webrtc,
  and follow it with call.supervision.* webhooks.
date: 2026-09-30T00:00:00.000Z
type: guide
---


## Summary

Call supervision puts a team lead onto a live call in your workspace. The supervisor hears both sides from the moment they join, and you choose how far they step in:

| Mode | Supervisor hears | Agent hears supervisor | Customer hears supervisor |
|---|---|---|---|
| `listen` | both sides | no | no |
| `whisper` | both sides | yes | no |
| `barge` | both sides | yes | yes |

There is one supervisor per call. The supervisor can be a browser running `@sautikit/webrtc`, a SIP device on one of your SIP credentials, or any phone number. Modes change only through the API: the supervisor's keypad cannot switch them, so a stray key press never puts a supervisor in front of your customer.

## Prerequisites

- **Permission to supervise.** An API key with the `calls.supervise` scope starts, switches and stops supervision; reading the current state needs `calls.read`. A signed-in console user needs the Owner or Admin role, or the Desk Supervisor licence. Device tokens are refused with `403 calls.supervise_forbidden`.
- **A live, answered call.** You can address it by its call UUID or its `HD_` session id, the same as every other `/v1/calls` route.
- **A supervisor to ring:** a `@sautikit/webrtc` identity (the `clientName` from the token your server mints), a SIP credential username, or a phone number in E.164.
- **Funds in the wallet.** A wallet at zero or below gets `402 wallet.insufficient_funds`.

---

## Steps

### Step 1: Start from your server

`calls.supervise()` in [`@sautikit/node`](/developers/sdks/node) wraps [Start call supervision](/developers/api/startCallSupervision). Name the supervisor and the mode they start in:

```js
import { SautikitClient } from "@sautikit/node";

const sautikit = new SautikitClient({ apiKey: process.env.SAUTIKIT_API_KEY });

const supervision = await sautikit.calls.supervise("HD_24d73b4518bc", {
  mode: "listen",
  supervisor: { type: "client", identity: "mary" },
  announce: "none",
  label: "Mary Wanjiku",
});

console.log(supervision.supervision_id, supervision.state); // "connecting"
```

`supervisor` is one of `{ type: "client", identity }`, `{ type: "sip", identity }` or `{ type: "phone", number }`. `label` (up to 64 characters) is the supervisor's name: it appears in every supervision event and in the agent's notice. The call answers `201`:

```json
{
  "supervision_id": "0f6d3c1e-5a8b-4c2d-9e7f-1a2b3c4d5e6f",
  "call_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",
  "session_id": "HD_24d73b4518bc",
  "supervisor_leg_id": "sv_7c1d2e3f",
  "mode": "listen",
  "announce": "none",
  "supervisor": { "type": "client", "identity": "mary", "label": "Mary Wanjiku" },
  "notify_agent": true,
  "state": "connecting",
  "created_at": "2026-09-30T09:15:02Z"
}
```

Sautikit then rings the supervisor. A second supervisor on the same call gets `409 calls.supervision_in_progress`, naming who is already there. A call that is not answered yet, or has ended, gets `409 calls.not_live`. The [API reference](/developers/api/startCallSupervision) lists every response.

### Step 2: Answer in the browser

When the supervisor works in your web app, let the page start the supervision itself. The browser SDK's token only opens the media socket and carries no API permissions, so the page relays start, mode changes and stop through your server, which holds the API key.

On your server, expose three routes that call `@sautikit/node`, always naming the signed-in user as the `client` supervisor. Your API key can supervise every call in the workspace, so your server is the permission check for your own users: decide there who may listen to whom.

```js
import express from "express";
import { SautikitClient } from "@sautikit/node";

const sautikit = new SautikitClient({ apiKey: process.env.SAUTIKIT_API_KEY });
const app = express();
app.use(express.json());

// Your own rule for who may supervise, checked on every route; then pass
// Sautikit errors (409, 402, 422...) back to the page.
const supervisorOnly = (handler) => async (req, res) => {
  if (!req.user?.canSupervise) return res.sendStatus(403);
  try {
    await handler(req, res);
  } catch (err) {
    res.status(err.status ?? 500).json({ code: err.code, message: err.message });
  }
};

app.post("/supervision/:callId", supervisorOnly(async (req, res) => {
  // req.user.clientName: the identity you minted this user's WebRTC token for.
  const s = await sautikit.calls.supervise(req.params.callId, {
    mode: req.body.mode,
    announce: req.body.announce,
    label: req.body.label,
    notifyAgent: req.body.notifyAgent,
    supervisor: { type: "client", identity: req.user.clientName },
  });
  res.json({ supervisionId: s.supervision_id, supervisorLegId: s.supervisor_leg_id, mode: s.mode });
}));

app.patch("/supervision/:callId", supervisorOnly(async (req, res) => {
  await sautikit.calls.setSupervisionMode(req.params.callId, req.body.mode);
  res.sendStatus(204);
}));

app.delete("/supervision/:callId", supervisorOnly(async (req, res) => {
  await sautikit.calls.stopSupervision(req.params.callId);
  res.sendStatus(204);
}));
```

In the page, pass those routes to the `Client` as its `supervision` backend, then call `client.supervise()`:

```js
import { Client } from "@sautikit/webrtc";

const api = (method, callId, body) =>
  fetch(`/supervision/${callId}`, {
    method,
    headers: { "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  }).then((r) => {
    if (!r.ok) throw new Error(`supervision ${method} failed: ${r.status}`);
    return r.status === 204 ? undefined : r.json();
  });

const client = new Client({
  token: session.token,
  endpoint: session.endpoint,
  protocol: session.protocol,
  turnServer: session.turnServer,
  supervision: {
    start: (callId, req) => api("POST", callId, req),
    setMode: (callId, mode) => api("PATCH", callId, { mode }),
    stop: (callId) => api("DELETE", callId),
  },
});

// From a click handler: the SDK needs microphone permission.
const sup = await client.supervise("HD_24d73b4518bc", { mode: "listen", label: "Mary Wanjiku" });
sup.on("ended", ({ reason }) => console.log("supervision ended:", reason));
```

The SDK answers the supervisor leg by itself. It only auto-answers a leg this client asked for and is still expecting, so an ordinary incoming call is never picked up behind the user's back. If the same user has other tabs open, they ring it as a normal `incoming` event carrying `supervisorLegId`, and stop ringing once one tab answers. Set `supervisionAutoAnswer: false` in the `Client` config to receive the leg as an `incoming` event and answer it yourself with `accept()`.

A SIP or phone supervisor needs none of this: their device rings like any other call and they pick it up.

### Step 3: Switch modes and stop

Move between modes at any point after the supervisor has answered. From the browser, use the `Supervision` object:

```js
await sup.setMode("whisper"); // coach the agent; the customer hears nothing
await sup.setMode("barge");   // join the conversation
await sup.stop();             // leave; the call carries on
```

From your server, the same operations are `calls.setSupervisionMode(callId, mode)` ([Change the supervisor's mode](/developers/api/changeCallSupervisionMode)) and `calls.stopSupervision(callId)` ([Remove the supervisor](/developers/api/stopCallSupervision)). A mode change while the supervisor is still ringing gets `409 calls.supervisor_not_connected`. Stopping is idempotent: it answers `204` even when nobody is supervising. `calls.getSupervision(callId)` ([Get the call's active supervision](/developers/api/getCallSupervision)) returns the active supervision, or `null`.

A supervisor who mutes their own microphone sends no audio, so the agent and the customer hear nothing from them in `whisper` or `barge` until they unmute.

### Step 4: Show the agent a banner

When the agent takes calls in a browser on `@sautikit/webrtc`, their tab receives a `supervision` event as a supervisor joins, changes mode and leaves, so your app can show a line such as "Mary Wanjiku is listening":

```js
const doing = { listen: "listening", whisper: "coaching you", barge: "on the call" };

client.on("supervision", (notice) => {
  if (notice.event === "ended") return hideBanner();
  showBanner(`${notice.supervisorName ?? "A supervisor"} is ${doing[notice.mode]}`);
});
```

`notice.event` is `started`, `mode_changed` (with `previousMode`) or `ended` (with `reason`). Notices go only to the tab that answered the call, nothing is sent while the supervisor is still ringing, and they are best effort with no replay: a tab that reconnects mid-call misses anything sent while it was away. Read the current state from your server with `calls.getSupervision()` if you need it after a reconnect.

Pass `notify_agent: false` (`notifyAgent: false` in the SDKs) to supervise without sending notices. That setting is independent of `announce`, which is the audible tone.

## Events

Supervision sends five workspace webhook events. Subscribe to them by name with [Create a webhook](/developers/api/createWebhook):

- [`call.supervision.ringing`](/developers/webhooks/call-supervision-ringing): the supervisor is being rung.
- [`call.supervision.started`](/developers/webhooks/call-supervision-started): the supervisor answered and is on the call.
- [`call.supervision.mode_changed`](/developers/webhooks/call-supervision-mode-changed): the mode changed through the API.
- [`call.supervision.ended`](/developers/webhooks/call-supervision-ended): the supervision is over, with the reason and billable seconds.
- [`call.supervision.failed`](/developers/webhooks/call-supervision-failed): the supervisor leg never connected.

These are Sautikit events about the supervisor. The call's own events on your number's `events_url` are unchanged.

## Announce, recording and billing

**Announce.** `announce` controls an audible tone when the supervisor joins and on every mode change: `none` (the default), `agent` (only the agent hears it) or `both` (the agent and the customer). The tone is never in the call recording, and the supervisor never hears it.

**Recording.** Supervision never starts a recording. On a recorded call, audio from `barge` is in the recording, because the customer heard it. `listen` and `whisper` are not.

**Billing.** The supervisor leg is billed like any call leg, from the moment the supervisor answers until they leave:

- From a browser or a SIP device: **KES 0.50 a minute**, metered per second.
- To a phone number: your normal **outbound call rate** for that number.

A supervisor who never answers, or a leg that fails, costs nothing. The charge appears on your statement as its own line, referencing the supervised call. If the wallet runs dry while a supervisor is on a call, Sautikit removes the supervisor and `call.supervision.ended` carries `reason: "insufficient_balance"`. Current prices are always at [/pricing](/pricing).

## Limits

- **One supervisor per call.** To hand over, the first supervisor leaves, then the next one starts.
- **The supervisor does not follow a transfer.** Any transfer that re-connects the call ends the supervision with `reason: "call_transferred"`: a `Redirect`, a queue handing the caller to another agent, or a SIP transfer. Start a new supervision on the call if you want to keep listening.
- **Some calls cannot be supervised.** Calls answered by an AI agent, conference rooms, and callers still waiting in a queue get `422 calls.unsupported_call_type`.
- **A supervisor who does not answer within 30 seconds** ends the supervision with `reason: "no_answer"`.
- **A SIP or browser supervisor must be registered.** An unregistered target gets `422 calls.supervisor_unreachable`.

## Next steps

- [Start call supervision](/developers/api/startCallSupervision): every field and response in the API reference.
- [Browser calling with WebRTC](/developers/guides/browser-calling-with-webrtc): mint tokens and put agents in the browser.
- [Verify webhook signatures](/developers/guides/verify-webhook-signatures): check that supervision events came from Sautikit.
- [Call monitoring, whisper and barge](/use-cases/call-monitoring-whisper-barge): how call-centre teams use supervision.
