> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# telephony.incoming / web.incoming

> 可即時設定來電組態的阻擋式 Webhook。

當來電撥入一個**未指派智慧體**的號碼，或網頁小工具工作階段透過
`mode="webhook"` 的可公開金鑰啟動時，ThunderPhone 會向你的
[舊版 webhook URL](/api-reference/organizations#legacy-single-url-webhook)
傳送一個**阻塞式**
`telephony.incoming` / `web.incoming` 請求，並最多等待 **10 秒**
以取得設定回應。使用此交換流程，為每通電話動態選擇提示、語音與工具——
如需端對端模式，請參閱[動態通話設定指南](/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>

此阻塞式交換流程沒有備援：如果你的處理常式回傳非 2xx 狀態、逾時，
或回傳未通過驗證的設定，通話將遭拒絕（電話不會接通；小工具工作階段
請求會以 `502`/`422` 失敗）。請快速回應——當你進行判斷時，來電者會
聽到回鈴音。

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

## 請求酬載

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

```json theme={null}
{
  "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 theme={null}
{
  "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 theme={null}
{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
```

| 欄位                            | 類型          | 必填 | 說明                                                                                                       |
| ----------------------------- | ----------- | -- | -------------------------------------------------------------------------------------------------------- |
| `prompt`                      | 字串          | 是  | 驅動智慧體的系統提示詞                                                                                              |
| `voice`                       | 字串          | 是  | 來自 [`GET /v1/voices`](/api-reference/agents#voices) 的語音 ID，例如 `john`。`voice_name` 可作為別名使用。未知語音會驗證失敗並拒絕通話 |
| `product`                     | 字串          | 否  | 預設為 `spark`。允許值：`spark`、`bolt`、`storm-base`、`storm-base-with-ack`、`storm-extra`、`storm-extra-with-ack`   |
| `thinking_level`              | 字串          | 否  | `minimal`、`base`（預設）或 `extra`。Storm 產品會覆寫此設定：`storm-extra*` 強制使用 `extra`，其他 `storm-*` 強制使用 `base`        |
| `audio_context_mode`          | 字串          | 否  | `full`（預設）或 `reduced`                                                                                    |
| `watchdog_enabled`            | 布林值         | 否  | 啟用此次通話的監督功能。預設為 `false`                                                                                  |
| `additional_audio_context`    | 布林值 \| null | 否  | 納入來電者音訊的最近幾個回合，而非僅納入最新回合，以較小的延遲與成本增加改善修正，以及拼寫或數字密集的資料蒐集。撥入工作階段預設開啟，撥出電話預設關閉；`null` 會保留預設值                |
| `storm_feedback_mode`         | 字串          | 否  | `none`、`acknowledgement`（預設）或 `tick`                                                                     |
| `language`                    | 字串          | 否  | `primary_language` 的簡寫                                                                                   |
| `primary_language`            | 字串          | 否  | 語言代碼，會進行正規化（預設為 `en`）。無法解析的代碼會拒絕通話                                                                       |
| `has_additional_languages`    | 布林值         | 否  | 預設為 `false`                                                                                              |
| `additional_languages`        | 字串陣列        | 否  | 智慧體可切換使用的其他語言                                                                                            |
| `background_track`            | 字串 \| null  | 否  | 環境音訊 ID 或 `null`                                                                                         |
| `acknowledgement_prompt_mode` | 字串          | 否  | `auto`（預設）或 `manual`（支援確認的 Storm 產品）                                                                     |
| `acknowledgement_prompt`      | 字串          | 否  | 當 `acknowledgement_prompt_mode="manual"` 時使用                                                             |
| `silence_interval_seconds`    | 整數 \| null  | 否  | 5–120。來電者靜默多久後進行確認，以秒為單位                                                                                 |
| `silence_max_checkins`        | 整數 \| null  | 否  | 1–10                                                                                                     |
| `silence_checkins_enabled`    | 布林值         | 否  | 預設為 `true`                                                                                               |
| `connect_tone_enabled`        | 布林值         | 否  | 預設為 `false`                                                                                              |
| `voicemail_action`            | 字串          | 否  | `prompt`（預設）、`hangup` 或 `message`                                                                        |
| `voicemail_message`           | 字串          | 否  | 當 `voicemail_action="message"` 時使用                                                                       |
| `agent_name`                  | 字串          | 否  | 回報至控制台和小工具的顯示名稱                                                                                          |
| `org_name`                    | 字串          | 否  | 智慧體角色設定所使用的組織顯示名稱                                                                                        |
| `tools`                       | 陣列          | 否  | 內嵌函式工具結構描述（請參閱[函式工具](/zh-Hant/tools/overview)）                                                           |
| `call_id`                     | 整數          | 否  | 可選的請求通話 ID 回傳值；會被忽略                                                                                      |

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

由於 `prompt` 與 `voice` 為必填欄位，回傳 `{}` 或任何未通過驗證的
回應都會以 `422` 拒絕通話——此路徑沒有靜態智慧體備援機制（Webhook
模式中的號碼或金鑰沒有指派智慧體）。

***

## 回應大小限制

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

***

## 處理常式範例

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  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) theme={null}
  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 theme={null}
{
  "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">
    將多個 URL 訂閱至 `telephony.incoming` / `web.incoming`。
  </Card>

  <Card title="動態通話設定" icon="wand-magic-sparkles" href="/zh-Hant/guides/dynamic-call-config">
    依來電者設定提示、工具與 A/B 測試的模式。
  </Card>
</CardGroup>
