---
title: "Sematkan widget web"
description: "Tambahkan agen suara ke situs pemasaran atau dukungan Anda dengan satu tag skrip — pengunjung dapat berbicara langsung dengannya dari browser, tanpa memerlukan nomor telepon."
---

Widget web memberi pengunjung situs Anda percakapan klik-untuk-berbicara
dengan agen AI, menggunakan mikrofon browser. Ini adalah SDK
JavaScript / React terpisah dengan [referensi SDK](/id/widget/overview)
sendiri — panduan ini berfokus pada penyiapan di sisi ThunderPhone yang diperlukan widget.

<Note>
  Anda dapat melakukan semua ini tanpa cURL: halaman dashboard **Widget Web**
  (`/dashboard/web-widgets`) membuat widget, menetapkan mode
  dan agennya, mengelola domain yang diizinkan, serta memberikan cuplikan embed.
</Note>

## Prasyarat

<Steps>
  <Step title="Buat agen">
    Agen yang Prompt dan suaranya akan menjalankan sesi widget. Atur
    `widget_enabled: true` (default).
  </Step>
  <Step title="Tentukan mode perutean">
    - `mode="agent"` — satu agen statis per kunci. Paling sederhana.
    - `mode="webhook"` — server Anda memilih agen per pengunjung melalui
      [webhook `web.incoming`](/id/webhooks/call-incoming). Gunakan ini untuk
      pengguna yang sudah masuk, pengujian A/B, atau perutean per halaman.
  </Step>
  <Step title="Cantumkan domain yang diizinkan">
    Cantumkan setiap nama host yang akan menyematkan widget. Browser mengirim
    origin halaman dengan setiap permintaan sesi, sehingga daftar ini mencegah kunci Anda
    berfungsi ketika seseorang menyalinnya ke situs lain. Ini bukan
    autentikasi: skrip di luar browser dapat mengirim header `Origin`
    apa pun. `localhost` tidak diizinkan secara otomatis di produksi; untuk
    menguji secara lokal terhadap API produksi, tambahkan `localhost` ke dalam
    daftar.
  </Step>
  <Step title="Pilih override browser">
    Tentukan pengaturan sesi yang dapat dikirim oleh halaman Anda. Kunci baru tidak menerima
    override; lihat [Override browser](#browser-overrides).
  </Step>
</Steps>

## Buat kunci yang dapat dipublikasikan

<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>

Respons mencakup `key` yang diawali dengan `pk_live_...`. **Kunci yang dapat dipublikasikan
bersifat publik secara desain** — aman untuk disertakan dalam bundle front-end Anda.
Lihat [referensi kunci yang dapat dipublikasikan](/api-reference/publishable-keys) untuk
semua field.

<Warning>
  `allowed_domains` harus berisi setidaknya satu entri. `*.example.com`
  cocok dengan subdomain (misalnya `api.example.com`) tetapi **bukan** domain
  tanpa subdomain. Wildcard tanpa domain seperti `*` atau `*.*` ditolak.
</Warning>

## Tambahkan widget ke situs Anda

Tiga opsi integrasi dibahas dalam
[dokumentasi SDK widget](/id/widget/overview):

<CardGroup cols={3}>
  <Card title="Komponen React" icon="react" href="/id/widget/react">
    `<ThunderPhoneWidget publishableKey="pk_live_..." />`.
  </Card>
  <Card title="Hook headless" icon="circle-nodes" href="/id/widget/headless-hook">
    `useThunderPhone()` untuk UI kustom.
  </Card>
  <Card title="Tag skrip CDN" icon="code" href="/id/widget/cdn-script-tag">
    `ThunderPhone.mount({...})` untuk situs tanpa bundler.
  </Card>
</CardGroup>

Ketiganya menerima `publishableKey` yang sama dan merender tombol mikrofon
beserta elemen audio saat panggilan berlangsung.

## Override browser

Widget dapat mengirim empat pengaturan per sesi dari halaman:

| Pengaturan | Prop widget | Yang diubah |
|---------|-------------|-----------------|
| `context` | `context` | Teks dari halaman, ditambahkan ke Prompt agen sebagai referensi |
| `variables` | tidak ada (hanya `POST /v1/widget/session` mentah) | Nilai untuk placeholder `{{variable}}` dalam Prompt |
| `voice` | `voice` | Suara agen untuk sesi ini |
| `language` | `language` | Bahasa yang digunakan saat sesi dimulai |

Kunci publishable Anda bersifat publik, sehingga siapa pun yang memilikinya dapat mengirimkan pengaturan ini, termasuk dari skrip yang memalsukan domain Anda. Karena itu, masing-masing dinonaktifkan hingga Anda mengizinkannya pada kunci: centang di bawah **Override browser** saat Anda membuat atau mengedit widget di dashboard, atau tetapkan `allowed_overrides` melalui [API kunci publishable](/api-reference/publishable-keys). Izinkan hanya yang benar-benar dikirim oleh halaman Anda.

Permintaan sesi yang membawa pengaturan yang tidak diizinkan oleh kunci akan gagal dengan `400` dan menyebutkan pengaturan tersebut:

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

Pemeriksaan ini berlaku di kedua mode. Dalam mode webhook, pengaturan yang diizinkan juga diteruskan ke webhook `web.incoming` Anda (`context` diterima sebagai `website_context`). Kunci yang dibuat sebelum pengaturan ini tersedia mengizinkan keempatnya, sehingga penyematan yang ada tetap berfungsi; nonaktifkan yang tidak Anda gunakan.

`context` widget dipotong hingga 12.000 karakter (sekitar 3.400 token teks bahasa Inggris umum) dan diperhitungkan dalam [biaya tambahan ukuran Prompt](/id/guides/billing-and-topups). Agen diinstruksikan untuk memperlakukannya sebagai konten halaman, bukan sebagai instruksi.

## Webhook mode widget

Saat `mode="webhook"`, ThunderPhone memanggil `webhook_url` Anda pada setiap
awal sesi dengan payload `web.incoming`. Kembalikan konfigurasi agen
yang ingin Anda jalankan untuk pengunjung tersebut — konfigurasi ini mengikuti
[skema respons](/id/webhooks/call-incoming) yang sama seperti panggilan
telepon:

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

Anda dapat mencampurkan konteks dari sesi Anda sendiri (pelanggan mana yang sedang menjelajah,
halaman mana yang mereka buka) ke dalam prompt, serta mengganti agen pada setiap peluncuran.

## Pantau sesi

Sesi widget muncul di
[`GET /v1/calls`](/api-reference/calls#list-calls) dengan
`direction="widget"` — transkrip, rekaman, penilaian, dan
penagihan yang sama seperti panggilan telepon. Filter berdasarkan `direction` untuk membuat
dashboard khusus widget.

---

## Langkah berikutnya

<CardGroup cols={2}>
  <Card title="Referensi SDK widget" icon="window-maximize" href="/id/widget/overview">
    Detail integrasi React / hook / CDN.
  </Card>
  <Card title="Konfigurasi dinamis per panggilan" icon="bolt" href="/id/guides/dynamic-call-config">
    Terapkan alur `mode="webhook"` dari awal hingga akhir.
  </Card>
  <Card title="Referensi kunci yang dapat dipublikasikan" icon="key" href="/api-reference/publishable-keys">
    Setiap kolom pada sumber daya kunci.
  </Card>
  <Card title="API sesi mikrofon" icon="microphone" href="/api-reference/mic-sessions">
    Lewati widget; gunakan LiveKit secara langsung untuk UI kustom.
  </Card>
</CardGroup>
