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

當來電接通一個**未指派
智能體**的號碼，或網頁小工具工作階段以
`mode="webhook"` 的可公開金鑰啟動時，ThunderPhone 會向你的
[舊版 webhook URL](/api-reference/organizations#legacy-single-url-webhook)
發送一個**阻塞式**
`telephony.incoming` / `web.incoming` 請求，並最多等待 **10 秒**
以取得設定回應。使用這個交換程序，為每通電話動態選擇提示詞、語音及工具——
完整流程請參閱[動態通話設定指南](/yue/guides/dynamic-call-config)。

<Note>
  已訂閱的 [webhook 端點](/yue/webhooks/endpoints)亦會接收
  `telephony.incoming` / `web.incoming`——適用於**每一個**來電
  及網頁工作階段，不論是否已設定智能體——但這些傳送屬於附帶通知，
  包含 `event_id`，絕不會阻塞處理程序。
  只有舊版單一 URL webhook 會承載本頁所述的設定交換程序。端點通知格式請參閱
  [事件目錄](/yue/webhooks/events)。
</Note>

此阻塞式交換程序沒有備援：如你的處理程序傳回非 2xx 狀態、逾時，
或傳回未能通過驗證的設定，通話將被拒絕（電話不會接通；小工具
工作階段請求會以 `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 | 小工具傳遞每個工作階段的頁面內容時提供 |

<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` |
| `language` | string | 否 | `primary_language` 的簡寫 |
| `primary_language` | string | 否 | 語言代碼，會經標準化處理（預設為 `en`）。無法解析的代碼會拒絕通話 |
| `has_additional_languages` | boolean | 否 | 預設為 `false` |
| `additional_languages` | array of string | 否 | 智能體可切換至的額外語言 |
| `native_voice_switching` | boolean | 否 | 預設為 `false`。當通話切換至另一種語言時，改用該語言的母語語音（按性別配對），而非保留已設定的語音 |
| `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` |
| `connect_tone_enabled` | boolean | 否 | 預設為 `false` |
| `voicemail_action` | string | 否 | `prompt`（預設）、`hangup` 或 `message` |
| `voicemail_message` | string | 否 | 當 `voicemail_action="message"` 時使用 |
| `agent_name` | string | 否 | 顯示於控制台及小工具中的名稱 |
| `org_name` | string | 否 | 用於智能體角色設定的機構顯示名稱 |
| `tools` | array | 否 | 內嵌函式工具結構描述（請參閱 [函式工具](/yue/tools/overview)） |
| `call_id` | integer | 否 | 可選擇回傳請求中的通話 ID；會被忽略 |

<Note>
  未知的頂層鍵會被靜默**忽略**——拼錯欄位名稱不會令設定遭拒絕，
  但該欄位不會生效。此處不接受發言順序及 `max_hold_seconds`；
  它們只能在[智能體](/api-reference/agents)本身設定。
</Note>

由於 `prompt` 及 `voice` 為必填欄位，傳回 `{}` 或任何未能通過驗證的
回應都會以 `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>

---

## 包含函數工具的回應

附加工具，讓 AI 可在對話期間呼叫你的 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
  密鑰**簽署。請參閱
  [函數工具](/yue/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="/yue/webhooks/call-complete">
    非阻塞式通話結束事件。
  </Card>
  <Card title="功能工具" icon="screwdriver-wrench" href="/yue/tools/overview">
    `tools[]` 的完整 JSON schema 及已簽署端點合約。
  </Card>
  <Card title="Webhook 端點" icon="bolt" href="/yue/webhooks/endpoints">
    為 `telephony.incoming` / `web.incoming` 訂閱多個 URL。
  </Card>
  <Card title="動態通話設定" icon="wand-magic-sparkles" href="/yue/guides/dynamic-call-config">
    按來電者設定提示、工具及 A/B 測試的模式。
  </Card>
</CardGroup>
