---
title: "Nhúng tiện ích web"
description: "Thêm một tác nhân AI giọng nói vào trang marketing hoặc hỗ trợ của bạn chỉ với một thẻ script — khách truy cập trò chuyện trực tiếp với tác nhân đó từ trình duyệt, không cần số điện thoại."
---

Tiện ích web cung cấp cho khách truy cập trang của bạn một cuộc trò chuyện nhấp để nói
với một tác nhân AI, sử dụng microphone của trình duyệt. Đây là một
SDK JavaScript / React riêng biệt với [tài liệu tham khảo SDK](/vi/widget/overview)
riêng — hướng dẫn này tập trung vào thiết lập phía ThunderPhone mà tiện ích cần.

<Note>
  Bạn có thể thực hiện tất cả việc này mà không cần cURL: trang dashboard
  **Tiện ích web** (`/dashboard/web-widgets`) tạo tiện ích, đặt chế độ
  và tác nhân, quản lý miền được phép, rồi cung cấp cho bạn đoạn mã nhúng.
</Note>

## Điều kiện tiên quyết

<Steps>
  <Step title="Tạo tác nhân AI">
    Tác nhân AI có prompt và giọng nói sẽ chạy phiên widget. Đặt
    `widget_enabled: true` (mặc định).
  </Step>
  <Step title="Chọn chế độ định tuyến">
    - `mode="agent"` — một tác nhân AI tĩnh cho mỗi khóa. Đơn giản nhất.
    - `mode="webhook"` — máy chủ của bạn chọn tác nhân AI cho mỗi khách truy cập thông qua
      [webhook `web.incoming`](/vi/webhooks/call-incoming). Dùng cách này cho
      người dùng đã đăng nhập, kiểm thử A/B hoặc định tuyến theo từng trang.
  </Step>
  <Step title="Liệt kê các miền được phép">
    Nêu mọi hostname sẽ nhúng widget. Trình duyệt gửi origin của
    trang cùng mỗi yêu cầu phiên, vì vậy danh sách này ngăn khóa của bạn
    hoạt động khi ai đó sao chép khóa sang một trang khác. Đây không phải là
    xác thực: một script bên ngoài trình duyệt có thể gửi bất kỳ header `Origin`
    nào. `localhost` không tự động được cho phép trong môi trường production; để
    kiểm thử cục bộ với API production, hãy thêm `localhost` vào danh sách.
  </Step>
  <Step title="Chọn các ghi đè của trình duyệt">
    Quyết định các cài đặt phiên mà trang của bạn có thể gửi. Khóa mới không chấp nhận
    ghi đè nào; xem [Ghi đè của trình duyệt](#browser-overrides).
  </Step>
</Steps>

## Tạo khóa có thể công khai

<CodeGroup>
```bash Static agent
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Marketing site (prod)",
    "mode":            "agent",
    "agent_id":        12,
    "allowed_domains": ["example.com", "*.example.com"],
    "allowed_overrides": ["context"]
  }'
```

```bash Dynamic via webhook
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Support (dynamic)",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-hook",
    "allowed_domains": ["support.example.com"]
  }'
```
</CodeGroup>

Phản hồi bao gồm một `key` bắt đầu bằng `pk_live_...`. **Khóa có thể công khai
được thiết kế để công khai** — an toàn để đưa vào gói front-end của bạn.
Xem [tài liệu tham khảo khóa có thể công khai](/api-reference/publishable-keys) để biết
toàn bộ trường.

<Warning>
  `allowed_domains` phải chứa ít nhất một mục. `*.example.com`
  khớp với miền con (ví dụ: `api.example.com`) nhưng **không** khớp với
  miền gốc. Ký tự đại diện độc lập như `*` hoặc `*.*` sẽ bị từ chối.
</Warning>

## Thêm tiện ích vào trang của bạn

Ba tùy chọn tích hợp được trình bày trong
[tài liệu SDK tiện ích](/vi/widget/overview):

<CardGroup cols={3}>
  <Card title="Thành phần React" icon="react" href="/vi/widget/react">
    `<ThunderPhoneWidget publishableKey="pk_live_..." />`.
  </Card>
  <Card title="Hook không giao diện" icon="circle-nodes" href="/vi/widget/headless-hook">
    `useThunderPhone()` cho giao diện người dùng tùy chỉnh.
  </Card>
  <Card title="Thẻ script CDN" icon="code" href="/vi/widget/cdn-script-tag">
    `ThunderPhone.mount({...})` cho trang không dùng trình đóng gói.
  </Card>
</CardGroup>

Cả ba đều nhận cùng `publishableKey` và hiển thị nút microphone
cùng phần tử âm thanh trong cuộc gọi.

## Ghi đè từ trình duyệt

Widget có thể gửi bốn tùy chọn cho mỗi phiên từ trang:

| Tùy chọn | Prop của widget | Nội dung thay đổi |
|---------|-------------|-----------------|
| `context` | `context` | Văn bản từ trang, được thêm vào prompt của tác nhân AI làm nội dung tham chiếu |
| `variables` | không có (chỉ `POST /v1/widget/session` thô) | Giá trị cho các phần giữ chỗ `{{variable}}` trong prompt |
| `voice` | `voice` | Giọng nói của tác nhân AI trong phiên này |
| `language` | `language` | Ngôn ngữ mà phiên bắt đầu sử dụng |

Khóa có thể công khai của bạn là công khai, nên bất kỳ ai có khóa đều có thể gửi các cài đặt này, kể cả từ một script giả mạo miền của bạn. Vì vậy, từng cài đặt đều bị tắt cho đến khi bạn cho phép chúng trên khóa: chọn trong mục **Ghi đè trên trình duyệt** khi tạo hoặc chỉnh sửa widget trên dashboard, hoặc đặt `allowed_overrides` thông qua [API khóa có thể công khai](/api-reference/publishable-keys). Chỉ cho phép những gì trang của bạn thực sự gửi.

Yêu cầu phiên mang một tùy chọn mà khóa không cho phép sẽ thất bại
với `400` và nêu tên tùy chọn đó:

```json
{
  "error": "override_not_allowed",
  "fields": ["voice"],
  "message": "This publishable key does not allow the browser to set `voice`. ..."
}
```

Kiểm tra này áp dụng ở cả hai chế độ. Trong chế độ webhook, một tùy chọn được phép
cũng được chuyển tiếp đến webhook `web.incoming` của bạn (`context` đến dưới dạng
`website_context`). Các khóa được tạo trước khi tùy chọn này tồn tại cho phép cả
bốn tùy chọn, vì vậy các bản nhúng hiện có vẫn hoạt động; hãy tắt những tùy chọn bạn không dùng.

`context` của widget bị cắt ngắn ở 12.000 ký tự (khoảng 3.400 token của văn bản tiếng Anh
thông thường) và được tính vào
[phụ phí kích thước prompt](/vi/guides/billing-and-topups).
Tác nhân AI được hướng dẫn coi đây là nội dung trang, không phải chỉ dẫn.

## Webhook chế độ tiện ích

Khi `mode="webhook"`, ThunderPhone gọi `webhook_url` của bạn mỗi khi
phiên bắt đầu với payload `web.incoming`. Trả về cấu hình tác nhân
bạn muốn chạy cho khách truy cập đó — cấu hình này tuân theo cùng
[lược đồ phản hồi](/vi/webhooks/call-incoming) như cuộc gọi điện thoại:

```json
{
  "prompt":  "You are a VIP concierge for Jane Doe.",
  "voice":   "john",
  "product": "storm-base",
  "tools":   [ /* per-customer tools */ ]
}
```

Bạn có thể kết hợp context từ phiên của riêng mình (khách hàng nào đang duyệt,
họ đang ở trang nào) vào prompt, và thay đổi tác nhân theo từng đợt triển khai.

## Theo dõi phiên

Phiên widget xuất hiện trong
[`GET /v1/calls`](/api-reference/calls#list-calls) với
`direction="widget"` — có cùng bản ghi, bản ghi âm, chấm điểm và
thanh toán như cuộc gọi điện thoại. Lọc theo `direction` để xây dựng
bảng điều khiển chỉ dành cho widget.

---

## Bước tiếp theo

<CardGroup cols={2}>
  <Card title="Tài liệu tham khảo SDK widget" icon="window-maximize" href="/vi/widget/overview">
    Chi tiết tích hợp React / hook / CDN.
  </Card>
  <Card title="Cấu hình động cho từng cuộc gọi" icon="bolt" href="/vi/guides/dynamic-call-config">
    Triển khai luồng `mode="webhook"` từ đầu đến cuối.
  </Card>
  <Card title="Tài liệu tham khảo khóa có thể công khai" icon="key" href="/api-reference/publishable-keys">
    Mọi trường trong tài nguyên khóa.
  </Card>
  <Card title="API phiên mic" icon="microphone" href="/api-reference/mic-sessions">
    Bỏ qua widget; điều khiển trực tiếp LiveKit cho giao diện người dùng tùy chỉnh.
  </Card>
</CardGroup>
