> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voicebip.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voicebip.com/_mcp/server.

# @voicebip/web — Browser SDK

> Embed a live AI voice call in any web page in 10 lines of JavaScript.

The `@voicebip/web` package handles WebRTC signaling, mic access, SSE transcript streaming, and call lifecycle so you don't have to. The package is not yet published to npm — this page previews the planned API.

## Installation (Coming Soon)

```bash
npm install @voicebip/web
```

No build-time native dependencies. ESM-only; requires a bundler (Vite, webpack, esbuild, Next.js, etc.) or a browser that supports ES modules natively.

## In the Meantime

Use the [Browser Calls (WebRTC)](/guides/browser-calls) guide to wire WebRTC directly — the guide includes a complete minimal JavaScript example that works today without any SDK dependency.

## Prerequisites

You need a **scoped token** — a short-lived JWT that authorises one call to one agent. Mint it server-side via `@voicebip/sdk` and pass it to the browser through your own API route.

See [Browser Calls (WebRTC)](/guides/browser-calls) for the full server + browser walkthrough.

---

## Quick start

```typescript
import { VoicebipCall } from '@voicebip/web';

// 1. Get a scoped token from your server
const { token, ice_servers, agent_id } = await fetch('/api/voice-token', {
  method: 'POST',
  body: JSON.stringify({ agent_id: 'agt_WX1Y_abcdef123456' }),
  headers: { 'Content-Type': 'application/json' },
}).then((r) => r.json());

// 2. Create and start the call
const call = new VoicebipCall({ token, agentId: agent_id, iceServers: ice_servers });

call.on('state', (s) => console.log('state →', s));
call.on('transcript', (t) => console.log(`[${t.role}] ${t.text}`));
call.on('ended', (ms) => console.log('ended after', ms, 'ms'));
call.on('error', (e) => console.error(e));

await call.start(); // resolves once connected; rejects on setup error

// Later:
call.setMuted(true);
await call.hangup();
```

---

## API Reference

### `new VoicebipCall(opts)`

| Option         | Type               | Default                    | Notes                                                                                  |
| -------------- | ------------------ | -------------------------- | -------------------------------------------------------------------------------------- |
| `token`        | `string`           | —                          | **Required.** Scoped JWT from `POST /v1/webrtc/token`.                                 |
| `agentId`      | `string`           | —                          | **Required.** Must match the token's `webrtc_agent_id` claim.                          |
| `iceServers`   | `IceServer[]`      | Google STUN                | Pass the `ice_servers` array from the token response unchanged.                        |
| `audioElement` | `HTMLAudioElement` | auto-created               | Where the agent's audio plays. The SDK appends a hidden `<audio autoplay>` if omitted. |
| `apiBase`      | `string`           | `https://api.voicebip.com` | Override for sandbox or self-hosted.                                                   |
| `source`       | `string`           | `web_sdk`                  | Hint attached to the `call.initiated` webhook payload.                                 |

### `call.start(): Promise<void>`

Requests mic access, creates the WebRTC peer connection, POSTs the SDP offer to `/v1/webrtc/offer`, applies the answer, and opens the transcript SSE stream.

* Resolves once `connectionState === 'connected'`.
* Rejects (and emits `error`) on any setup failure.
* Throws `Error` if called from any state other than `idle`.

### `call.hangup(): Promise<void>`

Closes the peer connection, stops all mic tracks, and closes the SSE stream. Idempotent — safe to call multiple times.

### `call.setMuted(muted: boolean): void`

Enables or disables the local microphone track. The remote peer hears silence; no signaling event is sent.

### `call.state: CallState`

Read-only. One of `'idle' | 'connecting' | 'connected' | 'ended' | 'error'`.

### `call.on(event, handler): () => void`

Registers an event handler. Returns an **unsubscribe function**.

```typescript
const unsub = call.on('transcript', (t) => console.log(t));
// later:
unsub();
```

---

## Events

| Event        | Payload                | When                                                          |
| ------------ | ---------------------- | ------------------------------------------------------------- |
| `state`      | `CallState`            | Every state transition.                                       |
| `call`       | `string` (call_id)     | Once the SDP exchange completes and a `call_id` is assigned.  |
| `transcript` | `TranscriptEvent`      | Each transcript line from the SSE stream.                     |
| `ended`      | `number` (duration ms) | Exactly once when the call ends, from any cause.              |
| `error`      | `Error`                | On setup or runtime error, before the state moves to `error`. |

### `TranscriptEvent`

```typescript
interface TranscriptEvent {
  role: 'agent' | 'caller';
  text: string;
  timestamp: string;  // ISO 8601 UTC
  is_final: boolean;  // true = committed line; false = streaming partial
}
```

---

## Types

```typescript
type CallState = 'idle' | 'connecting' | 'connected' | 'ended' | 'error';

interface IceServer {
  urls: string[];
  username?: string;
  credential?: string;
}

interface VoicebipCallOptions {
  token: string;
  agentId: string;
  iceServers?: IceServer[];
  audioElement?: HTMLAudioElement;
  apiBase?: string;
  source?: string;
}
```

---

## State machine

```
idle
 └─ start() ──→ connecting
                 └─ connected ──→ ended
                 └─ error     ──→ ended
```

`call.on('ended', ms)` fires exactly once regardless of how the call ends (remote hang-up, network drop, `hangup()`, or an error in `start()`).

---

## Security

* The SDK refuses to operate without a scoped token. Passing a `pk_live_*` workspace key is rejected at the API gateway with `401 UNAUTHENTICATED`.
* The token authorises **one** `POST /v1/webrtc/offer` for **one** agent. A mismatch between `agentId` and the token's `webrtc_agent_id` claim returns `403 PERMISSION_DENIED`.
* Tokens expire in ≤10 minutes. Mint a fresh token for each call session — do not cache them.
* TURN relay endpoints are embedded in the `ice_servers` response — pass them through to the SDK unchanged.

---

## What this SDK does not do

* **No UI.** No call buttons, no transcript widget, no audio visualisations. Build your own using the events above.
* **No React-specific helpers.** A `useVoicebipCall()` hook will ship in `@voicebip/web-react`.
* **No call control beyond mute + hangup.** Hold, transfer, and DTMF are server-side operations — use `@voicebip/sdk` for those.
* **No IE or Safari \< 14.** WebRTC + EventSource required.

---

## Changelog

| Version | Notes                                                                                         |
| ------- | --------------------------------------------------------------------------------------------- |
| 0.1.0   | Initial release — `VoicebipCall`, `start`, `hangup`, `setMuted`, `on`, SSE transcript stream. |