---
title: "telephony.incoming / web.incoming"
description: "Webhook pemblokiran yang membentuk konfigurasi panggilan masuk secara real time."
---

Saat panggilan telepon masuk mencapai nomor **tanpa agen
yang ditetapkan**, atau sesi widget web dimulai pada kunci yang dapat dipublikasikan dalam
`mode="webhook"`, ThunderPhone mengirimkan permintaan **yang memblokir**
`telephony.incoming` / `web.incoming` ke
[URL webhook lama Anda](/api-reference/organizations#legacy-single-url-webhook)
dan menunggu respons konfigurasi: hingga **13 detik** untuk
panggilan telepon, termasuk satu percobaan ulang, dan hingga **10 detik** untuk sesi
widget (lihat [Batas waktu dan percobaan ulang](#timeouts-and-retries)). Gunakan
pertukaran ini untuk memilih Prompt, suara, dan alat secara dinamis per panggilan —
lihat [panduan konfigurasi panggilan dinamis](/id/guides/dynamic-call-config)
untuk pola menyeluruh.

<Note>
  [Endpoint webhook](/id/webhooks/endpoints) yang berlangganan juga menerima
  `telephony.incoming` / `web.incoming` — untuk **setiap** panggilan masuk
  dan sesi web, baik dikonfigurasi dengan agen maupun tidak — tetapi
  pengiriman tersebut adalah notifikasi fire-and-forget dengan `event_id`,
  dan tidak pernah memblokir. Hanya webhook lama dengan satu URL yang
  membawa pertukaran konfigurasi pada halaman ini. Bentuk notifikasi endpoint
  tersedia di [katalog peristiwa](/id/webhooks/events).
</Note>

## Batas waktu dan percobaan ulang

Jawablah dengan cepat: penelepon menunggu di saluran saat Anda memutuskan.

Untuk panggilan telepon (`telephony.incoming`):

- Upaya pertama dapat memakan waktu hingga **10 detik**.
- Jika mengalami batas waktu habis, tidak dapat terhubung, atau menerima respons
  `5xx`, `408`, atau `429`, ThunderPhone mencoba sekali lagi. Percobaan ulang mendapatkan sisa
  dari batas waktu **13 detik** untuk seluruh pertukaran: sekitar 3 detik setelah
  upaya pertama yang mengalami batas waktu habis, hampir seluruh 13 detik setelah kegagalan cepat.
  Tidak ada percobaan ulang jika sisa waktunya kurang dari 2 detik.
- Respons `4xx` lainnya, respons yang melebihi
  [batas ukuran](#response-size-limit), atau `2xx` yang isi responsnya bukan
  konfigurasi valid bersifat final dan tidak dicoba ulang.
- Kedua upaya mengirim isi respons yang sama, header `X-ThunderPhone-Signature`
  yang sama, dan header `X-ThunderPhone-Idempotency-Key` yang sama
  (`telephony-incoming-<call_id>`), sehingga handler yang memiliki efek samping
  dapat mengenali percobaan ulang.

Tidak ada agen cadangan. Jika tidak ada konfigurasi yang dapat digunakan, panggilan
langsung diakhiri alih-alih membiarkan penelepon berada di saluran hening.
Panggilan tersebut muncul dalam riwayat panggilan sebagai panggilan gagal yang tidak ditagihkan dengan
`end_reason` `inbound_config_webhook_failed`, dan peristiwa
[`telephony.complete`](/id/webhooks/call-complete) biasa dikirim.

Sesi widget (`web.incoming`) mendapatkan satu upaya hingga 10
detik. Jika gagal, permintaan sesi gagal dengan `502` atau `422`.

<Warning>
  **Panggilan yang dikonfigurasi melalui webhook tidak membawa pengumuman
  persetujuan ThunderPhone.** Panggilan yang dikonfigurasi melalui pertukaran ini
  melewati pengumuman awal panggilan tingkat agen dan secara tegas dikecualikan dari
  kerangka pengumuman persetujuan ThunderPhone (Ketentuan Layanan, bagian
  "Perekaman dan persetujuan"). Organisasi Anda sepenuhnya bertanggung jawab atas
  setiap pemberitahuan dan persetujuan terkait perekaman, pemantauan, partisipasi
  AI, dan identifikasi penelepon yang diwajibkan pada panggilan ini — panggilan
  tetap dapat direkam, ditranskripsikan, dianalisis, dan ditangani oleh AI.
  Buat pengungkapan yang diperlukan dalam alur panggilan Anda sendiri sebelum
  mengaktifkan jalur ini.
</Warning>

## Payload permintaan

Untuk panggilan telepon (`telephony.incoming`):

```json
{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
```

| Kolom | Tipe | Deskripsi |
|-------|------|-------------|
| `call_id` | integer | ID panggilan — tetap sama di seluruh peristiwa untuk panggilan ini |
| `from_number` | string | Nomor penelepon E.164 |
| `to_number` | string | Tujuan E.164 (salah satu nomor ThunderPhone Anda) |

Untuk sesi widget web (`web.incoming`), `data` mengidentifikasi
halaman yang menyematkan widget, bukan nomor telepon:

```json
{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
```

| Bidang | Tipe | Deskripsi |
|-------|------|-------------|
| `call_id` | integer | ID panggilan |
| `origin_domain` | string | Asal halaman yang menghosting widget, sebagaimana dikirim oleh peramban. Klien non-peramban dapat mengirim nilai apa pun, jadi jangan anggap ini sebagai bukti asal permintaan |
| `publishable_key_prefix` | string | Karakter pertama dari kunci yang dapat dipublikasikan yang membuka sesi |
| `language`, `primary_language` | string | Ada saat sesi widget meminta override bahasa |
| `voice` | string | Ada saat sesi widget meminta override suara |
| `website_context` | string | Ada saat widget meneruskan konteks halaman per sesi |

Bidang override (`language`, `voice`, `website_context`, dan `variables`
peramban) hanya tiba saat kunci yang dapat dipublikasikan mengizinkannya di
[override peramban](/id/guides/embed-web-widget). Permintaan
sesi yang menyertakan override yang tidak diizinkan oleh kunci tersebut ditolak
sebelum webhook ini dipanggil.

<Note>
  Widget mode webhook mengirimkan permintaan ini ke `webhook_url` milik
  kunci yang dapat dipublikasikan sendiri saat ditetapkan, dengan fallback ke
  URL webhook tingkat organisasi. Dalam kedua kasus, permintaan ditandatangani
  dengan `secret` webhook organisasi.
</Note>

---

## Skema respons

Kembalikan objek JSON yang menjelaskan konfigurasi agen untuk panggilan ini.
`prompt` dan `voice` wajib diisi; semua yang lain bersifat opsional.

```json
{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
```

| Bidang | Jenis | Wajib | Deskripsi |
|-------|------|----------|-------------|
| `prompt` | string | ya | Prompt sistem yang mengarahkan agen |
| `voice` | string | ya | ID suara dari [`GET /v1/voices`](/api-reference/agents#voices), misalnya `john`. `voice_name` diterima sebagai alias. Suara yang tidak dikenal gagal validasi dan panggilan ditolak |
| `product` | string | tidak | Defaultnya `spark`. Diizinkan: `spark`, `bolt`, `storm-base`, `storm-base-with-ack`, `storm-extra`, `storm-extra-with-ack` |
| `thinking_level` | string | tidak | `minimal`, `base` (default), atau `extra`. Ditimpa untuk produk Storm: `storm-extra*` memaksa `extra`, `storm-*` lainnya memaksa `base` |
| `audio_context_mode` | string | tidak | `full` (default) atau `reduced` |
| `watchdog_enabled` | boolean | tidak | Aktifkan pengawasan untuk panggilan ini. Default `false` |
| `additional_audio_context` | boolean \| null | tidak | Sertakan beberapa giliran terakhir audio penelepon, bukan hanya giliran terbaru, untuk meningkatkan koreksi serta pengumpulan data yang banyak berisi ejaan/angka dengan sedikit tambahan latensi/biaya. Default aktif untuk sesi masuk dan nonaktif untuk panggilan telepon keluar; `null` mempertahankan default |
| `storm_feedback_mode` | string | tidak | `none`, `acknowledgement` (default), atau `tick`. Juga menentukan apa yang didengar penelepon saat model audio memproses giliran ketika `audio_tier` adalah `hearing` |
| `language` | string | tidak | Singkatan untuk `primary_language` |
| `primary_language` | string | tidak | Kode bahasa, dinormalisasi (default `en`). Kode yang tidak dapat diuraikan akan menolak panggilan |
| `has_additional_languages` | boolean | tidak | Default `false` |
| `additional_languages` | array of string | tidak | Bahasa tambahan yang dapat digunakan agen |
| `native_voice_switching` | boolean | tidak | Default `false`. Saat panggilan beralih ke bahasa lain, ganti ke suara native untuk bahasa tersebut (disesuaikan berdasarkan gender), alih-alih mempertahankan suara yang dikonfigurasi |
| `language_switching` | string \| null | tidak | `ask_first` (default) atau `follow_caller`; `null` sama dengan `ask_first`. Menentukan cara agen multibahasa menangani lawan bicara yang menggunakan bahasa lain yang didukungnya. `ask_first` langsung beralih ketika mereka meminta bahasa tersebut atau mengucapkan kalimat lengkap dalam bahasa itu, dan menanyakan apakah akan melanjutkan dalam bahasa tersebut setelah ucapan singkat, seperti "ya". `follow_caller` beralih ke bahasa terkonfigurasi apa pun yang mereka gunakan, bahkan hanya satu kata. Hanya berlaku ketika `additional_languages` ditetapkan |
| `audio_tier` | string \| null | tidak | Hanya `product: "bolt"`. `standard` (default; `""` dan `null` berarti sama) menjalankan tiga transkriptor (panggilan yang memproses PHI hanya menggunakan transkriptor yang tercakup BAA). `lean` menurunkan ke satu transkriptor, yang lebih sering salah dengar. `hearing` meningkatkan ke `standard` ditambah ansambel kuat model audio yang mendengarkan audio penelepon pada beberapa giliran, yang membutuhkan waktu sekitar 1–2 detik lebih lama. Produk lain mengabaikannya |
| `endpointing` | string \| null | tidak | `faster` (200 md), `fast` (300 md), `normal` (500 md, default), atau `patient` (800 md); `null` sama dengan `normal`. Menentukan berapa lama agen Bolt v2 menunggu setelah penelepon berhenti sebelum meminta model memberikan respons. Waktu tunggu yang lebih singkat dapat menjawab lebih cepat, tetapi mengirim lebih banyak permintaan yang menjadi usang oleh kata-kata penelepon berikutnya, sehingga meningkatkan biaya model. Produk lain mengabaikannya |
| `eagerness` | string \| null | tidak | Tidak digunakan lagi: nama `endpointing` hingga 19 Oktober 2026, tetapi masih dibaca. Diabaikan saat `endpointing` ditetapkan |
| `intelligence` | string \| null | tidak | `express`, `lite`, atau `standard` (default); `null` sama dengan `standard`. Tingkat kecerdasan agen `bolt` pada pipeline Bolt v2: menentukan model yang menulis responsnya. `express` memerlukan sekitar 600 md hingga kata pertama, lebih murah daripada Standard dan paling tidak andal dalam mengikuti instruksi; `lite` memerlukan sekitar 900 md, paling murah dan kurang andal; `standard` memerlukan sekitar 900 md dan terbaik dalam mengikuti instruksi. Respons Express dan Lite yang hanya akan memanggil tool diserahkan ke model Standard. Produk lain mengabaikannya |
| `background_track` | string \| null | tidak | ID audio ambient atau `null` |
| `acknowledgement_prompt_mode` | string | tidak | `auto` (default) atau `manual` (produk Storm-with-ack) |
| `acknowledgement_prompt` | string | tidak | Digunakan saat `acknowledgement_prompt_mode="manual"` |
| `silence_interval_seconds` | integer \| null | tidak | 5–120. Detik keheningan penelepon sebelum check-in |
| `silence_max_checkins` | integer \| null | tidak | 1–10 |
| `silence_checkins_enabled` | boolean | tidak | Default `true` |
| `silence_final_action` | string | tidak | Setelah check-in terakhir: `hang_up` (default) atau `transfer` ke `silence_transfer_destination` |
| `silence_transfer_destination` | string | tidak | Nama entri dalam `transfer_destinations` konfigurasi ini; wajib dengan `transfer`. Tanpa `transfer_destinations`, transfer tidak dapat dilakukan dan agen menutup panggilan |
| `silence_final_message` | string | tidak | ≤ 300 karakter, dibaca sebagaimana ditulis; `""` (default) menggunakan baris default |
| `connect_tone_enabled` | boolean | tidak | Default `false` |
| `pronunciation_dicts` | boolean \| null | tidak | Default `false`; `null` sama dengan `false`. Saat `true`, suara Cartesia panggilan menggunakan kamus pelafalan ThunderPhone untuk bahasa panggilan, jika tersedia. Berfungsi untuk panggilan telepon dan widget. Hanya berlaku untuk suara Cartesia milik ThunderPhone sendiri, bukan suara pada kunci API Cartesia Anda sendiri. Tidak digunakan dengan `agent_id`, karena pengaturan agen yang tersimpan berlaku |
| `voicemail_action` | string | tidak | `prompt` (default), `hangup`, atau `message` |
| `voicemail_message` | string | tidak | Digunakan saat `voicemail_action="message"` |
| `agent_name` | string | tidak | Nama tampilan yang dilaporkan ke dasbor dan widget |
| `org_name` | string | tidak | Nama tampilan organisasi untuk persona agen |
| `tools` | array | tidak | Skema function-tool inline (lihat [Alat Fungsi](/id/tools/overview)) |
| `transfer_destinations` | array of object \| null | tidak | Hingga 50 entri `{name, number}` yang dapat dialihkan oleh panggilan ini, divalidasi seperti [nomor transfer](/api-reference/agents#transfer-numbers) agen. Agen mengalihkan berdasarkan nama dan hanya ke entri tersebut; `[]` berarti tidak ada transfer. Hilangkan atau kirim `null` agar agen dapat mengalihkan ke nomor yang dipilihnya |
| `timezone` | string | tidak | Zona waktu IANA untuk panggilan ini, seperti `America/Chicago`. Agen menyatakan tanggal dan waktu dalam zona waktu tersebut, bukan zona waktunya sendiri. Juga diizinkan di samping `agent_id`. Nilai yang bukan zona IANA diabaikan. Lihat [Atur zona waktu per panggilan](/id/guides/dynamic-call-config) |
| `call_id` | integer | tidak | Gema opsional ID panggilan permintaan; diabaikan |

<Note>
  Kunci tingkat atas yang tidak dikenal akan **diabaikan** secara diam-diam — nama
  kolom yang salah ketik tidak menolak konfigurasi, tetapi tidak diterapkan. Urutan
  bicara dan `max_hold_seconds` tidak diterima di sini; keduanya hanya dapat
  dikonfigurasi pada [Agen](/api-reference/agents) itu sendiri.
</Note>

Karena `prompt` dan `voice` wajib, mengembalikan `{}` atau respons apa pun
yang gagal validasi akan menolak panggilan: panggilan telepon diakhiri
seperti dijelaskan dalam [Batas waktu dan percobaan ulang](#timeouts-and-retries), dan permintaan sesi
widget gagal dengan `422`. Tidak ada fallback agen statis pada jalur ini
(nomor atau kunci dalam mode webhook tidak memiliki agen yang ditetapkan).

---

## Batas ukuran respons

<Warning>
  Respons konfigurasi dibatasi hingga **5 MiB**. Jika handler
  mengembalikan respons yang lebih besar, termasuk dengan status
  `2xx`, ThunderPhone melaporkan bahwa respons melebihi batas dan
  menolak panggilan atau sesi widget. Pertahankan respons hanya pada field
  yang diperlukan untuk penyiapan panggilan; host data besar di balik function tool atau
  layanan lain alih-alih menyematkannya dalam konfigurasi.
</Warning>

---

## Contoh handler

<CodeGroup>
```python Python (FastAPI)
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)

    event = json.loads(body)
    if event["type"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
```

```javascript Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);
```
</CodeGroup>

---

## Respons dengan function tool

Tambahkan tool agar AI dapat memanggil API Anda di tengah percakapan:

```json
{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}
```

<Tip>
  Permintaan endpoint tool ditandatangani dengan **secret webhook organisasi yang sama**
  yang menandatangani pertukaran ini. Lihat
  [Function Tools](/id/tools/overview) untuk bentuk yang tepat dan format
  permintaan yang ditandatangani.
</Tip>

---

## Lembar ringkas tingkatan produk

| Produk | Latensi | Penalaran | Konfirmasi |
|---------|---------|-----------|-----------------|
| `spark` | Terendah | Dasar | — |
| `bolt` | Rendah | Ditingkatkan | — |
| `storm-base` | Sedang | Kuat | — |
| `storm-base-with-ack` | Sedang | Kuat | Pengisi otomatis saat berpikir |
| `storm-extra` | Lebih tinggi | Mendalam | — |
| `storm-extra-with-ack` | Lebih tinggi | Mendalam | Pengisi otomatis saat berpikir |

---

## Terkait

<CardGroup cols={2}>
  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/id/webhooks/call-complete">
    Peristiwa akhir panggilan yang tidak memblokir.
  </Card>
  <Card title="Function Tools" icon="screwdriver-wrench" href="/id/tools/overview">
    Skema JSON lengkap untuk `tools[]` dan kontrak endpoint yang ditandatangani.
  </Card>
  <Card title="Endpoint webhook" icon="bolt" href="/id/webhooks/endpoints">
    Berlangganan beberapa URL ke `telephony.incoming` / `web.incoming`.
  </Card>
  <Card title="Konfigurasi panggilan dinamis" icon="wand-magic-sparkles" href="/id/guides/dynamic-call-config">
    Pola untuk Prompt, alat, dan pengujian A/B per penelepon.
  </Card>
</CardGroup>
