---
title: "每通來電的動態設定"
description: "透過你自行控制的 webhook 中自訂邏輯，為每通來電個別選擇接聽智能體，或重寫其提示詞及設定。"
---

預設情況下，每個電話號碼及可公開金鑰均已指派固定智能體。當你需要按 **每位來電者** 或 **每位訪客** 自訂設定——VIP 路由、已登入用戶內容、A/B 提示詞測試——請切換至 webhook 模式，讓你的伺服器作出決定。

## 運作方式

1. 訂閱 [`telephony.incoming`](/yue/webhooks/events)
   （電話）或 [`web.incoming`](/yue/webhooks/events)（小工具）
   事件。兩者均為 **阻塞式** webhook：ThunderPhone 會在繼續通話前，最多等待
   10 秒接收你的回應。
2. ThunderPhone 會向你傳送 `{call_id, from_number, to_number}`（小工具
   工作階段會傳送小工具專用欄位而非電話號碼——請參閱
   [請求綱要](/yue/webhooks/call-incoming)）。
3. 你的伺服器會回傳智能體設定（提示詞、語音、
   產品、工具）。ThunderPhone 會將該設定用於這次通話。
4. 如你回傳 `{}`、逾時或發生錯誤，系統會以已固定指派的
   智能體作為備援。安全預設設定。

<Note>
  無論是電話通話（`telephony.incoming`）還是小工具
  工作階段（`web.incoming`），不論傳送至 webhook 端點
  還是舊版單一 URL webhook，運作方式完全相同。
</Note>

<Warning>
  **以內嵌 webhook 設定的通話不會播放 ThunderPhone 同意聲明。**
  此流程會略過智能體層級的通話開始聲明，並明確不適用於 ThunderPhone 的
  同意聲明框架（服務條款中的「錄音及同意」部分）。你的
  機構須全權負責這些通話的錄音、監察、AI 參與及來電者身分識別
  通知與同意。啟用內嵌 webhook 模式前，請在你自己的流程中提供相關內容——
  例如在提示詞的開場腳本中。引用已儲存
  `agent_id` 的 webhook 回應會使用該智能體的一般錄音及披露政策。
</Warning>

## 1. 設定 webhook 目的地

<Tabs>
<Tab title="電話通話">
如要設定電話號碼，請讓你的端點訂閱 `telephony.incoming`：

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

回應會包含一次性 `secret`——請妥善儲存；你會用它
驗證簽名。
</Tab>
<Tab title="網頁小工具">
如要設定小工具工作階段，請建立一個 `mode="webhook"` 的可公開金鑰，
並內嵌你的端點 URL：

```bash
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":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

小工具會在每次工作階段開始時 POST 至此 URL。
</Tab>
</Tabs>

## 2. 實作處理程式

三項實用原則：

- **驗證簽名**：每個請求均須驗證（請參閱
  [驗證 webhook 簽名](/yue/guides/verify-webhook-signatures)）。
  即使在開發環境亦不可省略——一次正確設定後即可重複使用。
- **快速回應**。十秒為硬性上限，而每一秒都是來電者聽到的靜默時間。如有需要可進行資料庫查詢，但
  不要同步呼叫下游 LLM——如需動態生成提示詞，請預先運算並快取。
- **妥善降級**。任何非預期狀態均應傳回 `{}`，讓靜態指派的智能體處理通話。

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

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

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

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

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

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

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

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

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. 回應結構

回應主體與
[來電回應結構](/yue/webhooks/call-incoming)
完全一致。常用欄位如下：

| 欄位 | 類型 | 說明 |
|-------|------|-------------|
| `prompt` | string（必填） | 智能體的系統提示 |
| `voice` | string（必填） | 來自 [`GET /v1/voices`](/api-reference/agents#voices) 的語音 ID |
| `product` | string | 預設為 `spark` |
| `background_track` | string \| null | 環境音效 ID |
| `acknowledgement_prompt_mode` | string | `auto` 或 `manual`（僅限帶確認提示的 Storm） |
| `acknowledgement_prompt` | string | 模式為 `manual` 時必填 |
| `tools` | array | 內嵌函數工具結構——請參閱[函數工具](/yue/tools/overview) |

<Note>
  單次通話的發言次序及 `max_hold_seconds` 無法透過
  webhook 回應設定。請在你所引用的
  [智能體](/api-reference/agents)上設定。
</Note>

### 保留已儲存的智能體並提供變數

回傳 `{"agent_id": 12, "variables": {"name": "Ada"}}`，即可使用該機構已儲存的
智能體及單次通話資料。其提示可包含 `{{name}}` 或
`{{name|Friend}}`。Webhook 變數會覆蓋請求層級變數；null
會使用預留位置的預設值，如未提供預設值則使用空白文字。最終
值及未解析的名稱會顯示於通話詳情和完成 webhook。已儲存智能體的回應僅接受
`agent_id` 及 `variables`。如包含 `prompt`，
回應會使用內嵌設定並忽略 `agent_id`（包括
null 或非整數中繼資料）；內嵌提示仍必須有效。內嵌
設定回應亦可包含 `variables`。已儲存智能體的回應會在電話及小工具通話中使用
智能體已部署的 A/B 分流，然後再呈現變數。請參閱[通話變數](/yue/guides/call-variables)
了解限制及工作階段 API 支援。攔截設定來自舊版
電話號碼／機構 URL 或 webhook 模式小工具金鑰；端點系統的
來電事件僅作通知用途。

## 模式

### 已登入用戶內容

在 webhook 模式小工具中，訪客頁面已知道其
身分。透過小工具 SDK 會轉送的查詢字串參數
（`?customer_id=123`）呼叫你的 webhook，並在伺服器端查詢客戶資料。

### A/B 提示推出

在自行實作前，請注意 ThunderPhone 提供原生
[實驗](/yue/guides/concepts)功能
（`/dashboard/experiments` 及智能體建立工具的 **A/B** 分頁），可
定義變體、分流流量，並按變體比較結果——
無需 webhook。

如仍需要由 webhook 端控制：將 `call_id` 雜湊 → 分組；
為 `0..49` 提供提示 A，為 `50..99` 提供提示 B。在你自己的資料庫中記錄
所選分組，稍後再與已完成通話的評分建立關聯。

### 按時間路由

辦公時間 → 「真人支援」智能體；非辦公時間 → 「記錄訊息」
智能體。在處理常式中直接根據 `new Date().getUTCHours()` 切換即可。

---

## 下一步

<CardGroup cols={2}>
  <Card title="來電 webhook 參考資料" icon="phone" href="/yue/webhooks/call-incoming">
    完整的請求及回應結構，包括所有設定金鑰。
  </Card>
  <Card title="驗證 webhook 簽名" icon="shield-check" href="/yue/guides/verify-webhook-signatures">
    一次正確設定 HMAC，隨處重用。
  </Card>
  <Card title="建立工具整合" icon="screwdriver-wrench" href="/yue/guides/build-tool-integration">
    結合動態路由與每個智能體的工具。
  </Card>
  <Card title="傳送語義" icon="bolt" href="/yue/webhooks/overview">
    重試、排序、逾時。
  </Card>
</CardGroup>
