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

# Web Calls

> Embed browser-based voice calls in your website so visitors can speak with your AI agent directly from the browser.

# Web Calls

Web calls let your users talk to your voice agent directly from a web browser — no phone number needed. Using WebRTC, thinnestAI streams audio between the browser and your agent in real time, creating a seamless voice experience embedded in your website or app.

## How Web Calls Work

```
User clicks "Call" button in your website
        ↓
  Browser requests a WebRTC session from thinnestAI
        ↓
  thinnestAI voice engine establishes audio stream
        ↓
  User speaks → STT → AI Agent → TTS → User hears response
        ↓
  Call ends when user hangs up or agent completes
```

No phone network is involved. Audio travels over the internet using WebRTC infrastructure powered by LiveKit.

## Option 1: Drop-in Embed Widget

For the fastest integration, use the thinnestAI embed widget. It handles all WebRTC complexity, permissions, and UI automatically.

### Script Tag Integration

Add this to your website:

```html theme={null}
<script
  src="https://api.thinnest.ai/embed/widget.js"
  data-publishable-key="pk_live_abc123xyz"
  data-agent-id="ag_pub_xyz789"
  data-widget-type="voice"
  async
></script>
```

This renders a floating call button. When clicked, it connects directly to your voice agent.

### Combined Chat + Voice

To offer both text and voice on the same page, set `data-widget-type` to `"combined"`:

```html theme={null}
<script
  src="https://api.thinnest.ai/embed/widget.js"
  data-publishable-key="pk_live_abc123xyz"
  data-agent-id="ag_pub_xyz789"
  data-widget-type="combined"
  async
></script>
```

> \[!NOTE]
> Widget appearance (colors, launcher style) can be customized from the **Deploy → Design** tab in your Agent Studio.

***

## Option 2: Custom UI with the Voice SDK

If you want full control over the user interface, build a custom interface using the `@thinnest-ai/voice-sdk`.

### Installation

```bash theme={null}
npm install @thinnest-ai/voice-sdk
```

### Starting a Call

```javascript theme={null}
import ThinnestVoice from "@thinnest-ai/voice-sdk";

// Initialize the SDK with your API key
const voice = new ThinnestVoice("YOUR_API_KEY");

// Start the call
await voice.start("YOUR_AGENT_PUBLIC_ID", {
  metadata: {
    user_name: "Jane Doe",
    page: "pricing"
  }
});
```

### Handling Events

The Voice SDK emits events that you can listen to in order to update your custom UI:

```javascript theme={null}
// Connection status updates
voice.on("status-change", ({ status }) => {
  console.log("Call status:", status); // "active", "ended", etc.
});

// Transcript events (what the user or agent said)
voice.on("transcript", (msg) => {
  console.log(`${msg.role}: ${msg.text}`);
});

// Real-time speech states
voice.on("speech-start", ({ speaker }) => {
  console.log(`${speaker} started speaking`);
});

voice.on("speech-end", ({ speaker }) => {
  console.log(`${speaker} stopped speaking`);
});

// Errors
voice.on("error", (error) => {
  console.error("Call error:", error.message);
});
```

### React Component Example

Here is a simple React implementation using the SDK:

```tsx theme={null}
import { useState, useEffect, useRef } from "react";
import ThinnestVoice from "@thinnest-ai/voice-sdk";

function VoiceAgent({ agentId }: { agentId: string }) {
  const voiceRef = useRef<ThinnestVoice>();
  const [status, setStatus] = useState("idle");
  const [transcripts, setTranscripts] = useState([]);

  useEffect(() => {
    voiceRef.current = new ThinnestVoice("YOUR_API_KEY");

    voiceRef.current.on("status-change", ({ status }) => setStatus(status));
    voiceRef.current.on("transcript", (msg) => {
      setTranscripts(prev => [...prev, msg]);
    });
    
    return () => voiceRef.current?.stop();
  }, []);

  const toggleCall = async () => {
    if (status === "active") {
      await voiceRef.current?.stop();
    } else {
      await voiceRef.current?.start(agentId);
    }
  };

  return (
    <div className="call-container">
      <button onClick={toggleCall} className={status === "active" ? "active" : ""}>
        {status === "active" ? "End Call" : "Start Call"}
      </button>
      
      <div className="transcript-box">
        {transcripts.map((msg, i) => (
          <p key={i} className={`msg ${msg.role}`}>
            <strong>{msg.role}:</strong> {msg.text}
          </p>
        ))}
      </div>
    </div>
  );
}
```

***

## Option 3: Backend-to-Backend Initialization

If you don't use the Voice SDK and prefer to get a LiveKit token from your backend to connect your own LiveKit client, you can call the underlying REST API.

### Step 1: Create a Session Token

```bash theme={null}
curl -X POST "https://api.thinnest.ai/v1/voice/session" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "YOUR_AGENT_PUBLIC_ID",
    "metadata": {
      "user_name": "Jane Doe"
    }
  }'
```

The response includes the `livekit_url`, `room_name`, and JWT `token`.

### Step 2: Connect

Pass these parameters to your LiveKit client implementation (see [WebRTC & LiveKit](/docs/deploy/webrtc) for examples).

***

## Browser Compatibility

Web calls use WebRTC, which is supported by:

| Browser       | Version | Notes                                |
| ------------- | ------- | ------------------------------------ |
| Chrome        | 56+     | Full support                         |
| Firefox       | 44+     | Full support                         |
| Safari        | 11+     | Full support                         |
| Edge          | 79+     | Full support (Chromium-based)        |
| Mobile Chrome | 56+     | Full support                         |
| Mobile Safari | 11+     | Requires user gesture to start audio |

### Microphone Permissions

The browser will prompt the user for microphone access. If access is denied, the Voice SDK emits an error event. Handle the permission denied case in your UI so the user understands why the call cannot start.

## Best Practices

1. **Request microphone permission gracefully** — If building a custom UI, wait until the user clicks "Call" to initialize the Voice SDK.
2. **Show visual feedback** — Indicate when the agent is speaking vs. listening. Users need to know when to talk.
3. **Display a live transcript** — Showing the conversation in text alongside the audio helps users follow along and improves accessibility.
4. **Test on mobile** — Mobile browsers have stricter autoplay policies. Audio playback requires a user gesture (tap/click) to start, which the Drop-in Widget and Voice SDK handle for you.

## Next Steps

* [Voice Configuration](/docs/voice/voice-configuration) — Customize the voice your web agent uses
* [Inbound Calls](/docs/voice/inbound-calls) — Let customers call you on a phone number too
* [Call Recording](/docs/voice/call-recording) — Record web calls for quality assurance
