> ## Documentation Index
> Fetch the complete documentation index at: https://docs.callers.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Talking avatars

> Give your web call agent a live, lip-synced face that renders in the visitor's browser, with layouts, framing, backgrounds and custom avatars.

A talking avatar puts a face on your agent during voice calls. The lips move in time with the agent's voice, the face blinks, breathes and reacts to the emotions in your script, and the whole thing renders inside the visitor's browser. No video is streamed and nothing runs on a server during the call, so the avatar adds no round trip to the conversation.

Use it when a face makes the conversation feel more personal: a receptionist on a clinic site, a guide on an onboarding page, a sales assistant on a pricing page. One attribute turns it on:

```html theme={"dark"}
<script src="https://cdn.callers.ai/sdk.js" data-agent="YOUR_AGENT_ID" data-avatar="maya"></script>
```

> **[Preview Maya, expanded](https://sdk-playground.callers.dev/?avatar=maya\&layout=expanded)**
>
> The playground with a stock avatar in the expanded layout.

> **[Preview John on a preset scene](https://sdk-playground.callers.dev/?avatar=john\&bg=preset%3Aai-office)**
>
> A stock avatar cut out over the AI office background.

## How it works

The avatar is driven by the same audio the visitor hears. Every animation frame, the SDK listens to the agent's voice, decides what the face should do, renders a new face with a small per-avatar model, and blends it into the avatar's photo.

<ol />

From the agent's voice to a rendered frame, entirely in the browser

### What drives the face

* **Lip sync from the audio.** The SDK reads the spectrum of the agent's voice each frame: loudness sets how far the mouth opens, the low formant bands pick the vowel shape (open, spread or rounded) and the high band catches sibilants and fricatives. This works with any voice and needs no training.
* **Lip shapes from the text.** On avatar calls the SDK asks Callers for the character timings of each sentence the agent speaks (the `X-Speech-Timings` call header). Audio alone can't tell a closed-lip "m" from a quiet moment, so the timings close the lips on m/b/p, bring the lip to the teeth on f/v and shape the vowels, while the audio still decides how far the mouth opens. Voices from providers that don't return timings fall back to audio-only lip sync.
* **Behavior.** A behavior controller adds everything the face does besides talking: blinks, small gaze shifts, head motion that follows the energy of the phrase, breathing, and a listening pose when the visitor speaks. When the visitor talks over the agent (barge-in), the face reacts and pending lip timings are dropped.
* **Emotions.** Each sentence in your script carries an emotion tag (for example `[cheerful]`). The SDK maps the tags to a few customer-facing looks and applies them when the words are heard, after the voice delay described below.

| Look | Script emotions (examples) | What changes |
| - | - | - |
| Joyful | `happy`, `cheerful`, `excited`, `grateful`, `playful` | Smile, raised brows, lifted cheeks |
| Warm | `warm`, `content`, `encouraging`, `reassuring`, `confident` | A softer smile |
| Surprised | `surprised`, `amazed`, `curious` | Raised brows |
| Empathetic | `sympathetic`, `concerned`, `apologetic`, `sad`, `anxious` | Inner brows up, smile relaxed |
| Thoughtful | `contemplative`, `confused`, `skeptical`, `determined` | Brows slightly lowered |
| Firm | `angry`, `frustrated`, `sarcastic`, `intense` | Brows lowered, smile relaxed. Never anger on the face. |
| Neutral | `neutral`, `calm`, anything unknown, sound tags like `[laughter]` | Resting face |

### How the face is rendered

Each avatar is a packaged folder: a photo, a manifest (`avatar.json`) and a model trained on that one photo. The model takes ten controls (mouth opening and shape, smile, blink, gaze, brow and cheek raise, and head pitch, yaw and roll) and returns a face crop. The SDK runs it with `onnxruntime-web` on **WebGPU** when the browser offers a GPU adapter and on **WebAssembly** otherwise, draws the crop over the photo on a 2D canvas with a feathered edge, and cross-fades each new face into the previous one so motion looks continuous at the display's refresh rate. The canvas follows the size of its container at the device pixel ratio (up to 2x).

New faces arrive every 15–40 ms on a GPU path. Inference runs asynchronously, so a slow device drops face updates rather than blocking the page.

### Keeping lips and voice together

The face always trails the sound a little: the audio has to be analyzed, the controls smoothed, the model run and a frame displayed. To hide that, the SDK plays the agent's voice through a short delay line that matches the measured face latency:

* The engine lag is measured live during the call by comparing the voice envelope with the rendered mouth opening (75 ms is used until enough speech has been heard).
* Inference time, one face interval and one display frame are added; the audio device's own output latency is subtracted, because it already delays what the visitor hears.
* The delay is capped at **300 ms** and changes glide slowly, because fast changes to a delay line audibly warp the voice.

All inputs are measured on the visitor's device, so the delay is as small as that device allows. `webCall.avatarStats` exposes the numbers (see [Performance](#performance-and-browser-support)).

> [!NOTE]
>
> While the avatar plays the voice, the call's own audio output is silenced so the visitor doesn't hear the voice twice. If the browser suspends audio (an app switch or a Siri interruption on iOS, a route change), playback hands back to the call automatically and returns when the avatar can play again.

### When the avatar appears

* **Voice calls only.** The avatar starts when the visitor clicks to call and stays for the whole call. Text conversations never show it.
* It lives in the call window's avatar area, or in your own element with [`avatarContainer`](#render-the-avatar-in-your-own-element).
* A misconfigured avatar (a typo in the name, a blocked download, no WebGL) logs a console warning starting with `Avatar` and the call continues without it. The avatar can never stop the widget from loading or a call from connecting.

## Choose an avatar

### Stock avatars

| Name | `avatar` value | Description | Gender |
| - | - | - | - |
| Maya | `maya` | Friendly young woman, warm and approachable, office setting | female |
| John | `john` | Friendly young man, relaxed and approachable, studio backdrop | male |

Pick one whose gender matches your agent's voice. The dashboard's test call and the playground list the matching avatars first and warn about a mismatch. The list is exported as `STOCK_AVATARS`, each entry `{ id, name, description, gender }`.

```html Script tag theme={"dark"}
<script src="https://cdn.callers.ai/sdk.js" data-agent="YOUR_AGENT_ID" data-avatar="john"></script>
```

```javascript Programmatic theme={"dark"}
import { WebCall, STOCK_AVATARS } from '@callers/web-sdk';

console.log(STOCK_AVATARS.map((avatar) => avatar.id)); // ['maya', 'john']

const webCall = new WebCall({ token, avatar: 'john' });
```

### Custom avatars

Callers can train an avatar for your brand from a portrait: a member of your team, a model, or a generated face. The avatar is added to the library under its own name (lowercase letters, digits and hyphens, 1–63 characters, for example `clinic-host-v2`) and you use it exactly like a stock one:

```html theme={"dark"}
<script src="https://cdn.callers.ai/sdk.js" data-agent="YOUR_AGENT_ID" data-avatar="clinic-host-v2"></script>
```

Contact [support@callers.ai](mailto:support@callers.ai) to request one. The photo matters: its expression becomes the avatar's resting face and can't be changed afterwards. A good portrait is front-facing head and shoulders, eyes open and looking into the camera, lips closed, a calm expression with at most a subtle closed-mouth smile, soft even light, nothing covering the face, landscape 4:3 at 1280×960 or larger, with real skin texture and a simple, softly blurred background. Only animate people who have consented.

#### Hosting a package yourself

`avatarUrl` (or `data-avatar-url`) points at a packaged avatar folder instead of a library name. The folder holds:

| File | What it is |
| - | - |
| `avatar.json` | The manifest: the names of the photo and model files, the face crop, the control names and limits. Produced by Callers when the avatar is trained. |
| The photo | The portrait the face is blended into. Never resize it: the crop in `avatar.json` is in photo pixels. |
| The model | The trained face model. Library models ship sealed and only load through the SDK. |
| A thumbnail (optional) | A small copy of the photo for pickers. The SDK itself never loads it. |

The SDK fetches `<avatarUrl>/avatar.json` and then the photo and model it names, so the host must allow cross-origin requests from your site (`Access-Control-Allow-Origin`). Packages are produced by Callers; use `avatarUrl` only with a package Callers gives you. When both `avatar` and `avatarUrl` are set, `avatarUrl` wins.

## Layouts

`avatarLayout` (or `data-avatar-layout`) sets the avatar's starting size in the call window.

| Value | Shows | Framing |
| - | - | - |
| `compact` (default) | A tile above the transcript | `face` |
| `expanded` | The full width of the call window | `photo` |
| `minimized` | A thumbnail in the call header | `face` |

Visitors change the size during the call with the **Expand avatar** / **Shrink avatar** and **Minimize avatar** buttons on the tile, and bring it back with **Show avatar** on the thumbnail. An unknown value logs a warning and uses `compact`.

```javascript theme={"dark"}
new WebCall({ token, avatar: 'maya', avatarLayout: 'expanded' });
```

## Framing

`avatarFraming` (or `data-avatar-framing`) controls how much of the photo is shown.

| Value | Shows | Default for |
| - | - | - |
| `face` | Head and shoulders, zoomed in on the model's face crop | `compact` and `minimized` layouts |
| `photo` | As much of the photo as fits the canvas | `expanded` layout and `avatarContainer` |

The framing never shows past the photo's edges: the visible region is centered on the face and shrunk or shifted to stay inside the photo. Set it only to override the default, for example `photo` in a compact tile when the portrait is tightly cropped already.

## Backgrounds

`avatarBackground` (or `data-avatar-background`) controls what shows behind the person.

| Value | Shows | Downloads the segmentation model |
| - | - | - |
| `photo` (default) | The photo's own background | No |
| `blur` | The photo's background, blurred, with the person painted out first so no outline shows | Yes |
| `transparent` | Nothing: your page (with `avatarContainer`) or the call window's tile shows through | Yes |
| `preset:<id>` | A ready-made scene from the library (see below). The id is case-insensitive. | Yes |
| A CSS color, for example `#0b1f3a`, `rgb(11 31 58)` or `navy` | That color | Yes |
| `url(https://example.com/office.jpg)` | That image, scaled to cover the avatar's area. `data:` and `blob:` URLs also work. | Yes |

### Preset scenes

Each scene is an empty, softly out-of-focus photo with a calm center for the head and shoulders. They are hosted with the avatar library and exported as `STOCK_BACKGROUNDS` (`{ id, name }`).

| Value | Scene |
| - | - |
| `preset:ai-office` | AI office: light wood, monitors with soft visualizations at the edges, plants |
| `preset:neutral-office` | Neutral office: white and light grey, a window, a plant |
| `preset:reception` | Front desk: a warm wood-slatted wall, pendant lights |
| `preset:clinic` | Clinic reception: white and pale sage walls, waiting chairs |
| `preset:home-office` | Home office: a bookshelf, a window with sheer curtains |
| `preset:studio` | Plain studio: a seamless light warm grey backdrop |

![Playground Look panel with Avatar, Size, Avatar background preset tiles and Launcher position controls](https://mintcdn.com/voxiaai/UGuY0UNIphM-6pUX/images/documentation/sdk/avatar-look-panel.png?fit=max\&auto=format\&n=UGuY0UNIphM-6pUX\&q=85\&s=fe63fb68b4e3d0fb497346b522cb6318)

The Look panel in the playground: stock avatars, layout, the background options including the six preset scenes, and the launcher position.

```html Script tag theme={"dark"}
<script src="https://cdn.callers.ai/sdk.js" data-agent="YOUR_AGENT_ID"
  data-avatar="maya" data-avatar-background="preset:clinic"></script>
```

```javascript Programmatic theme={"dark"}
new WebCall({ token, avatar: 'maya', avatarBackground: 'preset:clinic' });

// A brand color
new WebCall({ token, avatar: 'maya', avatarBackground: '#0b1f3a' });

// Your own image, written as CSS url()
new WebCall({ token, avatar: 'maya', avatarBackground: 'url(https://example.com/office.jpg)' });

// A file the visitor picked on your page (lives only in that tab)
new WebCall({ token, avatar: 'maya', avatarBackground: `url(${URL.createObjectURL(file)})` });
```

### How backgrounds work

Any value other than `photo` cuts the person out of every frame. When the call starts, the SDK downloads MediaPipe's selfie segmenter (a model of about 250 KB plus its runtime) and runs it on the GPU through WebGL, falling back to the CPU if the GPU delegate can't start. A new mask is computed at most every 50 ms and reused for the display frames in between, which keeps up with the avatar's calm head motion without taking over the main thread. The mask is smoothed over time and thresholded so the photo's original background never shows as a halo.

If the segmenter can't load or fails mid-call, the avatar keeps its photo background and logs a warning. The call is never affected.

> [!WARNING]
>
> Write image URLs as `url(...)`. A bare `https://…` value is ignored with a console warning, as is an unknown preset id or an unrecognized color. In every case the avatar falls back to `photo`. Values are checked when the widget is set up, so a typo warns at page load, not mid-call.

In the call window, a `transparent` avatar shows the widget's own surface through the tile, so it follows your [widget theme](https://docs.callers.ai/sdk/theming.md). To put the avatar on a background of your own design, render it in your own element.

## Render the avatar in your own element

Set `avatarContainer` (or `data-avatar-container`) to an element or a CSS selector, and the avatar renders there instead of in the call window. It fills the element, so size and shape the element with your own CSS. The default framing becomes `photo`. If the selector is invalid or matches nothing, the SDK logs a warning and shows the avatar in the call window instead.

Combine it with `transparent` to put the agent's face directly on your page design, or with a preset or color for a self-contained card.

```html theme={"dark"}
<div id="agent-face"></div>

<style>
  #agent-face {
    width: 360px;
    height: 480px;
    border-radius: 24px;
    overflow: hidden;
    background: radial-gradient(circle at 50% 30%, #f5f1ea, #d9d2c7);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
  }
  @media (max-width: 600px) {
    #agent-face { width: 100%; height: 60vw; }
  }
</style>

<script src="https://cdn.callers.ai/sdk.js"></script>
<script>
  const webCall = new CallersWebSdk.WebCall({
    token,
    avatar: 'maya',
    avatarContainer: '#agent-face',
    avatarBackground: 'transparent', // the gradient above shows through
  });
</script>
```

The avatar only renders while a voice call is connecting or live, so the element is empty before the visitor calls. Give it a placeholder (a still photo, a "Start a call" prompt) for the idle state.

## Configuration reference

Every option below is a `WebCall` constructor option with its script-tag equivalent. Options that aren't set fall back to the defaults shown. Unknown values for `avatarLayout` and `avatarFraming` warn and use the default; unknown background values warn and use `photo`.

**avatar** `string`

A library avatar by name: `maya`, `john`, or a custom name Callers trained for you. Lowercase letters, digits and hyphens only. Ignored when `avatarUrl` is set. Script tag: `data-avatar`.

**avatarUrl** `string`

URL of a packaged avatar folder (`avatar.json`, the model and the photo). Wins over `avatar`. Script tag: `data-avatar-url`.

**avatarLayout** `'compact' | 'expanded' | 'minimized'` (default: compact)

The avatar's starting size in the call window. Visitors can change it during the call. Script tag: `data-avatar-layout`.

**avatarFraming** `'face' | 'photo'`

`face` zooms in on head and shoulders; `photo` shows as much of the photo as fits. Defaults to `face` for `compact` and `minimized`, `photo` for `expanded` and for `avatarContainer`. Script tag: `data-avatar-framing`.

**avatarBackground** `string` (default: photo)

`photo`, `transparent`, `blur`, `preset:<id>`, a CSS color, or `url(<image>)`. Anything but `photo` downloads the segmentation model when the call starts. Script tag: `data-avatar-background`.

**avatarContainer** `HTMLElement | string`

An element, or a CSS selector for one, to render the avatar in instead of the call window. Falls back to the call window when the selector matches nothing. Script tag: `data-avatar-container` (selector only).

**avatarLibraryUrl** `string` (default: [https://cdn.callers.ai/avatars](https://cdn.callers.ai/avatars))

Where library avatars and `preset:` backgrounds are hosted. Leave unset unless Callers tells you otherwise. Script tag: `data-avatar-library`.

Related options that aren't avatar-specific: `launcherPosition` / `data-launcher-position` and `launcherOffset` / `data-launcher-offset` place the launcher and the call window that grows from it (see [Theming](https://docs.callers.ai/sdk/theming.md#launcher-position)). The complete option list is in [Configuration](https://docs.callers.ai/sdk/configuration.md#avatar).

## Runtime API

### `webCall.avatarStats`

During a call with an avatar, `avatarStats` returns live numbers; without an avatar, or before the model has loaded, it returns `null`.

| Field | Meaning |
| - | - |
| `backend` | `webgpu` or `wasm`: where the face model runs |
| `inferenceMs` | Average time to render one face |
| `speaking` | Whether the face is in its speaking state right now |
| `playbackDelayMs` | How much the voice is currently delayed to match the lips (0–300) |
| `engineLagMs` | The measured delay between the audio and the mouth controls |
| `displayFrameMs` | The measured display frame interval |
| `outputLatencyMs` | The audio device's own output latency, subtracted from the delay |
| `timedSentences` | How many sentences arrived with TTS character timings |
| `timedLips` | Whether the lips are currently shaped by timings (otherwise by audio alone) |
| `segmentationMs`, `compositeMs` | Average cost of cutting the person out and compositing a frame; 0 with the `photo` background |
| `segmentationDelegate` | `GPU`, `CPU`, or `null` with the `photo` background |

```javascript theme={"dark"}
const timer = setInterval(() => {
  const stats = webCall.avatarStats;
  if (stats) console.table(stats);
}, 2000);
webCall.onCallEnded = () => clearInterval(timer);
```

Use it on the devices your visitors use: a `wasm` backend with `inferenceMs` well above the display frame interval means the device is struggling and a smaller layout, or no avatar on that device class, is the better choice.

### `AvatarSession`

The SDK also exports `AvatarSession`, the avatar engine on its own, for showing an avatar outside the call widget. Callers uses it for the dashboard's test call. Most integrations should set `avatar` on `WebCall` and let the SDK manage the session; contact [support@callers.ai](mailto:support@callers.ai) before building on `AvatarSession` directly, because it needs the agent's audio stream, speech timings and emotions wired in by hand.

```typescript theme={"dark"}
import { AvatarSession, type AvatarSessionOptions, type AvatarStats } from '@callers/web-sdk';

// Start from a user gesture (a click), so the browser lets the audio context run.
const session = await AvatarSession.start({
  avatarUrl: 'https://cdn.callers.ai/avatars/maya', // a packaged avatar folder
  container: document.querySelector('#stage'),
  playAgentAudio: true,          // play the voice through the avatar, delayed to match the lips
  isMuted: () => speakerMuted,   // polled every frame
  framing: 'photo',              // 'face' | 'photo'
  background: 'preset:studio',   // any avatarBackground value
  onPlaybackChange: () => {},    // the browser suspended or resumed audio
});

session.setAgentStream(remoteStream);    // the agent's MediaStream; null to detach
session.setEmotion('cheerful');          // the emotion of the sentence starting now
session.addSpeechTimings(timings);       // character timings of a sentence (optional)
session.clearSpeechTimings();            // a new reply started; drop pending timings
session.interrupt();                     // the visitor talked over the agent
session.setFraming('face');
console.log(session.stats);              // AvatarStats
console.log(session.playsAgentAudio);    // true while the avatar is the one playing the voice
await session.dispose();                 // release the model, canvas and audio context
```

`AvatarSessionOptions`, `AvatarStats`, `AvatarBackground`, `StockAvatar` and `StockBackground` are exported as types. Only one `AvatarSession` can run at a time in a page, because the model runtime is shared.

## Events and callbacks

There are no avatar-specific callbacks. The avatar follows the call:

* `onCallStarted` / `onCallEnded` bracket the avatar's lifetime. The model starts loading on the dial click and the avatar is removed when the call ends.
* `onTranscriptChanged` messages carry `llmResponseSentences`, each with the sentence's `emotions` and, on avatar calls, `timings`. These are the same data the avatar uses, so you can mirror its state in your own UI.
* `onError` is not called for avatar problems. They are logged as console warnings starting with `Avatar`, and the call continues.
* `onDeafenChange` reflects the speaker toggle; the avatar honors it too, so muting the speaker also silences the avatar's playback.

## Privacy and security

* The avatar is rendered from the agent's audio in the visitor's browser. No camera, microphone or screen access is involved beyond the call's own microphone permission, and no video leaves the device.
* Library models are sealed: the files on the CDN can't be opened or run as models outside the SDK.
* Tell visitors they're talking to an AI. An avatar makes the agent feel more human, which makes disclosure more important, not less.

## Performance and browser support

### What gets downloaded

| Resource | From | When |
| - | - | - |
| The avatar package (photo, manifest and a model of roughly 20 MB) | `https://cdn.callers.ai/avatars` (or your `avatarUrl`) | On the dial click |
| The model runtime (`onnxruntime-web`) | `https://cdn.jsdelivr.net` | On the dial click, only when an avatar is configured |
| The segmentation runtime (`@mediapipe/tasks-vision`) | `https://cdn.jsdelivr.net` | On the dial click, only for backgrounds other than `photo` |
| The selfie segmentation model (about 250 KB) | `https://storage.googleapis.com` | Same as above |

The SDK bundle itself doesn't grow for sites that don't use avatars. If your site sets a Content Security Policy, allow `script-src` and `connect-src` for the hosts above (and `img-src` for `cdn.callers.ai` and any background image hosts).

### WebGPU and the fallback

The SDK asks the browser for a WebGPU adapter and runs the face model there when it gets one; otherwise it runs on WebAssembly on the CPU. Both paths produce the same faces; the CPU path renders fewer faces per second and uses more battery on phones. `webCall.avatarStats.backend` tells you which path a device took.

At the time of writing, WebGPU is available in current Chrome and Edge on desktop and Android, in Safari from version 26 on macOS, iOS and iPadOS, and in Firefox on Windows. Check [caniuse.com/webgpu](https://caniuse.com/webgpu) for your audience. Background separation uses WebGL, which is available everywhere the SDK runs.

### Guidance

* **Desktop with WebGPU:** any layout and background works well.
* **Recent phones:** use `compact` or `minimized`; prefer `photo` or a preset over `blur`, which costs the most per frame.
* **Older or low-end phones:** consider leaving the avatar off for that device class, or starting `minimized` so the visitor opts in. Check `inferenceMs` against `displayFrameMs` in `avatarStats`.
* The audio delay that keeps lips and voice together never exceeds 300 ms, so even a slow device doesn't make the conversation feel laggy; the face just updates less often.

## Try it in the playground

The [playground](https://docs.callers.ai/sdk/playground.md) runs the real widget against your agent and shows a live, silent preview of the avatar before you call.

**1. Open the playground with an avatar preloaded**

Start from a link, for example [`?avatar=maya&layout=expanded`](https://sdk-playground.callers.dev/?avatar=maya\&layout=expanded), and enter your **Agent ID** and secret. Add `&agent=YOUR_AGENT_ID` to any link below to skip typing it.

**2. Pick the look**

Under **Avatar**, choose **None**, **Maya**, **John** or **By name** (a custom avatar name, or a full `https://` package URL). Set the **Size** and the **Avatar background**: **Original**, **Blurred**, **None** (transparent), one of the six scenes, **Color** or **Image URL**. The preview updates as you click.

**3. Start a test call**

Click **Start test call**, allow the microphone and talk. Open **Developer** to watch the avatar stats while the call runs.

**4. Copy the code**

**Get code** shows the script tag and the npm code for exactly this setup. **Share demo** copies a link that reproduces it (without your secret).

![The SDK playground with the avatar pickers on the left and the site preview with the launcher on the right](https://mintcdn.com/voxiaai/UGuY0UNIphM-6pUX/images/documentation/sdk/avatar-playground-preview.png?fit=max\&auto=format\&n=UGuY0UNIphM-6pUX\&q=85\&s=de1aca9697cdacfc57c4673e5b4f51b2)

Pick an avatar and a background on the left; the preview on the right shows the widget on a sample page.

Link parameters for avatars (the full list is in [Playground](https://docs.callers.ai/sdk/playground.md#link-parameters)):

| Parameter | Values |
| - | - |
| `avatar` | `maya`, `john`, `none`, or a custom avatar name |
| `avatarUrl` | A packaged avatar folder URL |
| `layout` | `expanded` or `minimized` (the default is `compact`) |
| `bg` | `blur`, `transparent`, `preset:<id>`, `color` or `image` (the default is the photo) |
| `bgColor` | A hex color, with `bg=color` |
| `bgImage` | An image URL, with `bg=image` |

| Try | Link |
| - | - |
| John, minimized thumbnail | [`?avatar=john&layout=minimized`](https://sdk-playground.callers.dev/?avatar=john\&layout=minimized) |
| Maya on the clinic scene | [`?avatar=maya&bg=preset:clinic`](https://sdk-playground.callers.dev/?avatar=maya\&bg=preset%3Aclinic) |
| John with a blurred background | [`?avatar=john&bg=blur`](https://sdk-playground.callers.dev/?avatar=john\&bg=blur) |
| Maya on a brand color | [`?avatar=maya&bg=color&bgColor=#0b1f3a`](https://sdk-playground.callers.dev/?avatar=maya\&bg=color\&bgColor=%230b1f3a) |
| Maya over your own image | [`?avatar=maya&bg=image&bgImage=https://…`](https://sdk-playground.callers.dev/?avatar=maya\&bg=image) |

## In the dashboard

You don't need a website to see an avatar. Open the agent, click **Test** in the header and choose the **Web Call** tab. Pick an **Avatar** ("Shows a live talking avatar of the agent during the call") and an **Avatar background** (**Original photo**, **Blurred**, one of the preset scenes, or **Image URL…**), then start the call. Avatars that match the agent's voice are listed first; picking a mismatched one shows a warning.

The avatar is not part of the agent's widget settings: the embed snippet under **Channels** > **Widget** contains only `data-agent` and your variables. Add `data-avatar` and the other attributes on this page to the snippet yourself.

## Complete examples

```html Script tag theme={"dark"}
<!-- John, expanded, on the front-desk scene, launcher bottom-left -->
<script
  src="https://cdn.callers.ai/sdk.js"
  data-agent="YOUR_AGENT_ID"
  data-avatar="john"
  data-avatar-layout="expanded"
  data-avatar-background="preset:reception"
  data-launcher-position="bottom-left"
></script>
```

```javascript Programmatic (token) theme={"dark"}
import { WebCall } from '@callers/web-sdk';

// `token` comes from your server; see /sdk/authentication
const webCall = new WebCall({
  token,
  avatar: 'maya',
  avatarLayout: 'compact',
  avatarBackground: 'blur',
  onCallStarted: () => console.log('avatar backend:', webCall.avatarStats?.backend),
});
```

```jsx React (own container) theme={"dark"}
import { useEffect, useRef } from 'react';
import { WebCall } from '@callers/web-sdk';

export function AgentCard({ token }) {
  const faceRef = useRef(null);
  const webCallRef = useRef(null);

  useEffect(() => {
    webCallRef.current = new WebCall({
      token,
      avatar: 'maya',
      avatarContainer: faceRef.current, // an element works as well as a selector
      avatarBackground: 'transparent',
      showFab: false, // we provide our own button
    });
    return () => webCallRef.current?.destroy();
  }, [token]);

  return (
    <div className="agent-card">
      <div ref={faceRef} className="agent-face" />
      <button onClick={() => webCallRef.current?.call()}>Talk to Maya</button>
    </div>
  );
}
```

## Troubleshooting

| Symptom | Likely cause | What to do |
| - | - | - |
| No avatar, no console message | The call is a text conversation, or the visitor hasn't started a voice call yet | Avatars appear on voice calls only, from the dial click |
| `WebCall: invalid avatar name "…"` | The name has uppercase letters, spaces, dots or a path | Use lowercase letters, digits and hyphens, for example `maya` or `clinic-host-v2` |
| `Avatar unavailable, continuing without it` | The package or runtime couldn't load: a CSP or content blocker, an offline CDN, or a wrong `avatarUrl` | Allow the hosts under [Performance](#what-gets-downloaded); check that `<avatarUrl>/avatar.json` loads with CORS headers |
| `Avatar: unknown background "…", showing the photo` | The value isn't a keyword, preset, color or `url(...)` | Check the [backgrounds table](#backgrounds); wrap image URLs in `url(...)` |
| `Avatar: background images are written as url(...)` | A bare URL was passed | Use `url(https://…)` |
| `Avatar: unknown background preset "…"` | A preset id that isn't in `STOCK_BACKGROUNDS` | Use one of the six ids, case-insensitive |
| `Avatar: container "…" not found` | The selector matched nothing when the call started | Make sure the element exists before the visitor calls, or pass the element itself |
| `Avatar: background separation failed, showing the photo` | The segmenter failed on this device | Nothing to fix; the avatar keeps its photo background |
| Lips and voice out of sync, or the face is choppy | The device is on the CPU path, or is busy | Check `avatarStats.backend` and `inferenceMs`; use `minimized` on weak phones |
| The voice sounds doubled | Something else is also playing the call's audio | Only one `WebCall` (or `AvatarSession`) may play the agent's audio; don't attach the remote stream to your own audio element |
| The avatar vanished after the phone was locked or an app switch | iOS suspended the audio context | Expected: playback hands back to the call and the avatar resumes when the context is running again |

## FAQ

> **Does the avatar add latency to the conversation?**
>
> No. Nothing about the avatar runs on a server. The only timing change is in the visitor's browser: the agent's voice is delayed by the measured face latency, at most 300 ms, so that lips and sound land together. Turn-taking and the agent's response time are unchanged.

> **Can the avatar show on text conversations too?**
>
> No. The avatar is driven by the agent's voice, so it appears on voice calls only. It starts on the dial click of a voice call; a conversation that starts as text and is promoted to voice mid-call (the visitor turns on the microphone) continues without it.

> **Can I use my own video or a 3D character?**
>
> Not through this feature. Avatars are trained from a single portrait and rendered by the SDK. Contact [support@callers.ai](mailto:support@callers.ai) to discuss a custom avatar.

> **Does the visitor need a GPU?**
>
> No. Without WebGPU the model runs on WebAssembly. It still works; it renders fewer faces per second and uses more battery on phones.

> **Does the avatar work in headless mode or React Native?**
>
> The avatar renders into a DOM element, so it needs a browser page: the call window or your own `avatarContainer`. With the call window hidden (`hidden: true`), point `avatarContainer` at an element of your own; the avatar is appended to that element directly. See [Headless and React Native](https://docs.callers.ai/sdk/headless-and-react-native.md) for the rest of the headless setup.

> **Can visitors turn the avatar off?**
>
> They can minimize it to a header thumbnail during the call. To offer a full opt-out, create the `WebCall` with or without `avatar` based on a setting on your page.

> **Which voices get the best lip sync?**
>
> Any voice works from the audio alone. Voices whose provider returns character timings also close the lips on m/b/p and shape f/v and the vowels from the text. `avatarStats.timedLips` shows whether timings are in use on a call.

## Related

> **[Page Guide](https://docs.callers.ai/sdk/page-guide.md)**
>
> Let the agent see the page and point at elements while the avatar talks.

> **[Configuration](https://docs.callers.ai/sdk/configuration.md#avatar)**
>
> Every option and script-tag attribute.

> **[Playground](https://docs.callers.ai/sdk/playground.md)**
>
> Try every avatar, size and background with your agent.

> **[Installing the widget](https://docs.callers.ai/documentation/widget-agents/installing-the-widget.md)**
>
> The no-code setup in the dashboard.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.