---
title: "telephony.incoming / web.incoming"
description: "即時設定來電組態的阻塞式 Webhook。"
---

當一通來電撥打至**未指派語音智慧體的
號碼**，或網頁小工具工作階段在 `mode="webhook"` 的可發布金鑰上啟動時，ThunderPhone 會向你的
[舊版 webhook URL](/api-reference/organizations#legacy-single-url-webhook)
傳送**會阻塞的** `telephony.incoming` / `web.incoming`
請求，並等待設定回應：電話通話最長 **13 秒**，包含一次重試；小工具
工作階段最長 **10 秒**（請參閱[逾時與重試](#timeouts-and-retries)）。使用這項
交換機制，為每通電話動態選擇提示詞、語音和工具——
請參閱[動態通話設定指南](/zh-Hant/guides/dynamic-call-config)
以了解端對端模式。

<Note>
  已訂閱的 [webhook 端點](/zh-Hant/webhooks/endpoints) 也會收到
  `telephony.incoming` / `web.incoming`——針對**每一通**來電和網頁工作階段，
  無論是否已設定智慧體——但這些傳遞是包含 `event_id` 的即發即忘通知，
  不會造成阻塞。只有舊版單一 URL webhook 會承載本頁的設定交換流程。
  端點通知格式請參閱[事件目錄](/zh-Hant/webhooks/events)。
</Note>

## 逾時與重試

請快速回應：你做出決定時，來電者正在電話線上等待。

對於電話通話（`telephony.incoming`）：

- 第一次嘗試最多可能需要 **10 秒**。
- 如果逾時、無法連線，或收到 `5xx`、`408` 或 `429`
  回應，ThunderPhone 會再嘗試一次。重試可使用整個交換流程 **13 秒**
  預算中剩餘的時間：第一次嘗試逾時後約有 3 秒，快速失敗後則幾乎可使用完整的 13 秒。
  若剩餘時間不足 2 秒，則不會重試。
- 任何其他 `4xx` 回應、超過
  [大小限制](#response-size-limit)的回應，或本文不是有效設定的 `2xx`
  回應，皆為最終結果且不會重試。
- 兩次嘗試都會傳送相同的本文、相同的 `X-ThunderPhone-Signature`
  與相同的 `X-ThunderPhone-Idempotency-Key` 標頭
  （`telephony-incoming-<call_id>`），因此具有副作用的處理常式
  可以辨識重試。

沒有備援智慧體。若未收到可用的設定，通話會立即結束，而不會讓來電者在線上靜默等待。
它會在通話紀錄中顯示為失敗且未計費的通話，`end_reason` 為
`inbound_config_webhook_failed`，並會傳送一般的
[`telephony.complete`](/zh-Hant/webhooks/call-complete) 事件。

小工具工作階段（`web.incoming`）僅會進行一次最多 10
秒的嘗試。若失敗，工作階段請求會以 `502` 或 `422` 失敗。

<Warning>
  **透過 webhook 設定的通話不會包含 ThunderPhone 同意公告。**
  透過此交換流程設定的通話會略過智慧體層級的通話開始公告，且明確排除於
  ThunderPhone 的同意公告架構之外（服務條款的「錄音與同意」章節）。
  你的組織須全權負責這些通話所需的每一項錄音、監控、AI 參與及來電者身分識別
  通知與同意——這些通話仍可能由 AI 錄音、轉錄、分析及處理。在啟用此流程前，
  請將必要揭露內容納入你自己的通話流程。
</Warning>

## 請求承載資料

電話通話（`telephony.incoming`）：

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

| 欄位 | 類型 | 說明 |
|-------|------|-------------|
| `call_id` | integer | 通話 ID——在此通話的所有事件中保持一致 |
| `from_number` | string | E.164 來電者號碼 |
| `to_number` | string | E.164 目的地號碼（你的其中一個 ThunderPhone 號碼） |

網頁小工具工作階段（`web.incoming`）的 `data` 會識別嵌入頁面，而非電話號碼：

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

| 欄位 | 類型 | 說明 |
|-------|------|-------------|
| `call_id` | integer | 通話 ID |
| `origin_domain` | string | 瀏覽器傳送的代管小工具頁面來源。非瀏覽器用戶端可傳送任何值，因此請勿將其視為請求來源的證明 |
| `publishable_key_prefix` | string | 開啟工作階段的可公開金鑰開頭字元 |
| `language`, `primary_language` | string | 小工具工作階段要求覆寫語言時會出現 |
| `voice` | string | 小工具工作階段要求覆寫語音時會出現 |
| `website_context` | string | 小工具傳遞每個工作階段的頁面情境時會出現 |

覆寫欄位（`language`、`voice`、`website_context`，以及任何瀏覽器
`variables`）僅會在可公開金鑰於其[瀏覽器覆寫](/zh-Hant/guides/embed-web-widget)中允許時傳送。工作階段請求若包含該金鑰不允許的欄位，會在呼叫此 Webhook 前遭到拒絕。

<Note>
  Webhook 模式的小工具會在已設定時，將此請求傳送至可公開金鑰自己的
  `webhook_url`；否則會改用組織層級的 webhook URL。無論哪種方式，
  都會使用組織 webhook 的 `secret` 進行簽章。
</Note>

---

## 回應結構描述

傳回描述此次通話智慧體設定的 JSON 物件。
`prompt` 和 `voice` 為必填欄位；其餘皆為選填。

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

| 欄位 | 類型 | 必填 | 說明 |
|-------|------|----------|-------------|
| `prompt` | string | 是 | 驅動智慧體的系統提示詞 |
| `voice` | string | 是 | [`GET /v1/voices`](/api-reference/agents#voices) 中的語音 ID，例如 `john`。接受使用 `voice_name` 作為別名。未知語音無法通過驗證，並會拒絕通話 |
| `product` | string | 否 | 預設為 `spark`。允許值：`spark`、`bolt`、`storm-base`、`storm-base-with-ack`、`storm-extra`、`storm-extra-with-ack` |
| `thinking_level` | string | 否 | `minimal`、`base`（預設）或 `extra`。Storm 產品會覆寫此設定：`storm-extra*` 強制使用 `extra`，其他 `storm-*` 強制使用 `base` |
| `audio_context_mode` | string | 否 | `full`（預設）或 `reduced` |
| `watchdog_enabled` | boolean | 否 | 為此通話啟用監督。預設為 `false` |
| `additional_audio_context` | boolean \| null | 否 | 納入來電者最近幾輪的音訊，而非僅納入最新一輪，以小幅增加延遲與成本為代價，改善更正以及大量拼寫或數字資料的蒐集。來電工作階段預設啟用，撥出電話預設停用；`null` 會保留預設值 |
| `storm_feedback_mode` | string | 否 | `none`、`acknowledgement`（預設）或 `tick`。當 `audio_tier` 為 `hearing` 時，也決定音訊模型處理一輪對話期間來電者聽到的內容 |
| `language` | string | 否 | `primary_language` 的簡寫 |
| `primary_language` | string | 否 | 語言代碼，會經過標準化處理（預設為 `en`）。無法解析的代碼會拒絕通話 |
| `has_additional_languages` | boolean | 否 | 預設為 `false` |
| `additional_languages` | array of string | 否 | 智慧體可切換使用的其他語言 |
| `native_voice_switching` | boolean | 否 | 預設為 `false`。通話切換至其他語言時，改用該語言的母語語音（依性別配對），而非保留已設定的語音 |
| `language_switching` | string \| null | 否 | `ask_first`（預設）或 `follow_caller`；`null` 與 `ask_first` 相同。多語言智慧體如何處理對方說出其另一種語言。當對方要求使用該語言，或以該語言說出完整句子時，`ask_first` 會立即切換；若對方只說了簡短內容，例如「sí」，則會詢問是否要繼續使用該語言。`follow_caller` 會切換至對方所說的任何已設定語言，即使只有一個單字。僅在設定 `additional_languages` 時適用 |
| `audio_tier` | string \| null | 否 | 僅適用於 `product: "bolt"`。`standard`（預設；`""` 與 `null` 意義相同）會執行三個語音轉錄器（處理 PHI 的通話僅使用受 BAA 涵蓋的轉錄器）。`lean` 降級為一個轉錄器，因此更容易誤聽。`hearing` 升級為 `standard`，並加入強大的音訊模型集成系統，在部分輪次聆聽來電者音訊，這些輪次約需多花 1–2 秒。其他產品會忽略此設定 |
| `endpointing` | string \| null | 否 | `faster`（200 ms）、`fast`（300 ms）、`normal`（500 ms，預設）或 `patient`（800 ms）；`null` 與 `normal` 相同。Bolt v2 智慧體在來電者停止說話後、向模型要求回覆前的等待時間。較短的等待可更快回答，但會傳送更多可能因來電者下一句話而失效的請求，進而提高模型成本。其他產品會忽略此設定 |
| `eagerness` | string \| null | 否 | 已淘汰：截至 2026 年 10 月 19 日前 `endpointing` 的名稱，仍會讀取。設定 `endpointing` 時會忽略此欄位 |
| `intelligence` | string \| null | 否 | `express`、`lite` 或 `standard`（預設）；`null` 與 `standard` 相同。Bolt v2 管線中 `bolt` 智慧體的智慧層級：決定由哪個模型撰寫回覆。`express` 約 600 ms 輸出第一個字，比標準版便宜，但遵循指示的可靠性最低；`lite` 約 900 ms，最便宜且可靠性較低；`standard` 約 900 ms，最擅長遵循指示。若 Express 和 Lite 的回覆只會呼叫工具，則會交由 Standard 模型處理。其他產品會忽略此設定 |
| `background_track` | string \| null | 否 | 環境音訊 ID 或 `null` |
| `acknowledgement_prompt_mode` | string | 否 | `auto`（預設）或 `manual`（Storm-with-ack 產品） |
| `acknowledgement_prompt` | string | 否 | 當 `acknowledgement_prompt_mode="manual"` 時使用 |
| `silence_interval_seconds` | integer \| null | 否 | 5–120。來電者靜默多久後進行確認，單位為秒 |
| `silence_max_checkins` | integer \| null | 否 | 1–10 |
| `silence_checkins_enabled` | boolean | 否 | 預設為 `true` |
| `silence_final_action` | string | 否 | 最後一次確認後：`hang_up`（預設），或 `transfer` 至 `silence_transfer_destination` |
| `silence_transfer_destination` | string | 否 | 此設定中 `transfer_destinations` 項目的名稱；使用 `transfer` 時為必填。若沒有 `transfer_destinations`，則無法轉接，智慧體會掛斷電話 |
| `silence_final_message` | string | 否 | ≤ 300 個字元，會依原文讀出；`""`（預設）使用預設訊息 |
| `connect_tone_enabled` | boolean | 否 | 預設為 `false` |
| `pronunciation_dicts` | boolean \| null | 否 | 預設為 `false`；`null` 與 `false` 相同。設為 `true` 時，通話中的 Cartesia 語音會使用 ThunderPhone 為該通話語言提供的發音字典（如有）。適用於電話和小工具通話。僅適用於 ThunderPhone 自有的 Cartesia 語音，不適用於使用你自己的 Cartesia API 金鑰的語音。搭配 `agent_id` 時不會使用，會套用已儲存智慧體的設定 |
| `voicemail_action` | string | 否 | `prompt`（預設）、`hangup` 或 `message` |
| `voicemail_message` | string | 否 | 當 `voicemail_action="message"` 時使用 |
| `agent_name` | string | 否 | 回報至控制台和小工具的顯示名稱 |
| `org_name` | string | 否 | 智慧體角色的組織顯示名稱 |
| `tools` | array | 否 | 內嵌函式工具結構描述（請參閱[函式工具](/zh-Hant/tools/overview)） |
| `transfer_destinations` | array of object \| null | 否 | 此通話最多可轉接至 50 個 `{name, number}` 項目，驗證方式與智慧體的[轉接號碼](/api-reference/agents#transfer-numbers)相同。智慧體會依名稱轉接，且僅能轉接至這些項目；`[]` 表示不允許轉接。省略此欄位或傳送 `null`，即可讓智慧體轉接至其自行選擇的號碼 |
| `timezone` | string | 否 | 此通話使用的 IANA 時區，例如 `America/Chicago`。智慧體會依此時區陳述日期和時間，優先於智慧體自身的時區。也可與 `agent_id` 一起使用。非 IANA 時區的值會被忽略。請參閱[依通話設定時區](/zh-Hant/guides/dynamic-call-config) |
| `call_id` | integer | 否 | 請求通話 ID 的選用回傳值；會被忽略 |

<Note>
  未知的頂層鍵會被靜默**忽略**——拼錯的欄位
  名稱不會拒絕設定，只是不會套用。此處不接受發話順序
  和 `max_hold_seconds`；只能在[智慧體](/api-reference/agents)本身設定。
</Note>

由於 `prompt` 和 `voice` 為必填欄位，回傳 `{}` 或任何未通過驗證的
回應都會拒絕通話：電話會依照[逾時與重試](#timeouts-and-retries)所述結束，
而小工具工作階段要求會以 `422` 失敗。此路徑沒有靜態智慧體
備援方案（處於 Webhook 模式的號碼或金鑰未指派智慧體）。

---

## 回應大小限制

<Warning>
  設定回應的大小上限為 **5 MiB**。如果處理常式回傳較大的回應，即使狀態為 `2xx`，
  ThunderPhone 也會回報回應超出限制，並拒絕該通話或小工具工作階段。回應中只保留
  通話設定所需的欄位；大型資料應透過函式工具或其他服務提供，而非嵌入設定中。
</Warning>

---

## 處理常式範例

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

---

## 含函式工具的回應

附加工具，讓人工智慧可在對話途中呼叫你的 API：

```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>
  工具端點請求會使用與簽署此交換相同的**組織 Webhook
  密鑰**進行簽署。請參閱
  [函式工具](/zh-Hant/tools/overview)，瞭解確切結構與已簽署的
  請求格式。
</Tip>

---

## 產品方案速查表

| 產品 | 延遲 | 推理能力 | 回應確認 |
|---------|---------|-----------|-----------------|
| `spark` | 最低 | 基本 | — |
| `bolt` | 低 | 改善 | — |
| `storm-base` | 中等 | 強大 | — |
| `storm-base-with-ack` | 中等 | 強大 | 思考時自動填充回應 |
| `storm-extra` | 較高 | 深度 | — |
| `storm-extra-with-ack` | 較高 | 深度 | 思考時自動填充回應 |

---

## 相關內容

<CardGroup cols={2}>
  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/zh-Hant/webhooks/call-complete">
    非阻塞式的通話結束事件。
  </Card>
  <Card title="函式工具" icon="screwdriver-wrench" href="/zh-Hant/tools/overview">
    `tools[]` 的完整 JSON 結構描述，以及已簽署的端點合約。
  </Card>
  <Card title="Webhook 端點" icon="bolt" href="/zh-Hant/webhooks/endpoints">
    為 `telephony.incoming` / `web.incoming` 訂閱多個 URL。
  </Card>
  <Card title="動態通話設定" icon="wand-magic-sparkles" href="/zh-Hant/guides/dynamic-call-config">
    針對不同來電者設定提示、工具及 A/B 測試的模式。
  </Card>
</CardGroup>
