---
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/guides/dynamic-call-config)。

<Note>
  已订阅的 [Webhook 端点](/zh/webhooks/endpoints)也会接收
  `telephony.incoming` / `web.incoming`——针对**每一通**呼入电话
  和 Web 会话，无论是否已配置智能体——但这些投递是带有 `event_id` 的即发即弃通知，
  从不阻塞。只有旧版单 URL Webhook 会承载本页面中的配置交换。
  端点通知的数据结构请参阅[事件目录](/zh/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/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 小组件会话（`web.incoming`），`data` 会标识嵌入页面，而非电话号码：

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

| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `call_id` | 整数 | 通话 ID |
| `origin_domain` | 字符串 | 托管小组件的页面来源，由浏览器发送。非浏览器客户端可以发送任何值，因此请勿将其视为请求来源的证明 |
| `publishable_key_prefix` | 字符串 | 打开会话的可发布密钥的前几个字符 |
| `language`, `primary_language` | 字符串 | 当小组件会话请求语言覆盖时存在 |
| `voice` | 字符串 | 当小组件会话请求语音覆盖时存在 |
| `website_context` | 字符串 | 当小组件传递按会话设置的页面上下文时存在 |

仅当可发布密钥在其[浏览器覆盖](/zh/guides/embed-web-widget)中允许这些覆盖字段（`language`、`voice`、`website_context` 以及任何浏览器 `variables`）时，才会传入这些字段。携带密钥不允许的字段的会话请求会在调用此 webhook 之前被拒绝。

<Note>
  当设置了可发布密钥自身的 `webhook_url` 时，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` | 字符串 | 是 | 驱动智能体的系统提示词 |
| `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` | 字符串数组 | 否 | 智能体可切换到的其他语言 |
| `native_voice_switching` | 布尔值 | 否 | 默认为 `false`。当通话切换到另一种语言时，切换为该语言的母语语音（按性别匹配），而不是保留已配置的语音 |
| `language_switching` | 字符串 \| null | 否 | `ask_first`（默认）或 `follow_caller`；`null` 与 `ask_first` 相同。定义多语言智能体如何处理对方使用其另一种语言说话的情况。对方请求使用该语言或用该语言说出完整句子时，`ask_first` 会立即切换；对方只说了如“sí”之类的简短表达时，则会询问是否继续使用该语言。`follow_caller` 会切换到对方所说的任一已配置语言，即使对方只说了一个词。仅在设置 `additional_languages` 时适用 |
| `background_track` | 字符串 \| null | 否 | 环境音频 ID 或 `null` |
| `acknowledgement_prompt_mode` | 字符串 | 否 | `auto`（默认）或 `manual`（Storm-with-ack 产品） |
| `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` |
| `pronunciation_dicts` | 布尔值 \| null | 否 | 默认为 `false`；`null` 与 `false` 相同。当设为 `true` 时，通话中的 Cartesia 语音会使用 ThunderPhone 为该通话语言提供的发音词典（如有）。适用于电话和小组件通话。仅适用于 ThunderPhone 自有的 Cartesia 语音，不适用于您自己的 Cartesia API 密钥中的语音。不与 `agent_id` 一起使用，此时将应用已保存智能体的设置 |
| `voicemail_action` | 字符串 | 否 | `prompt`（默认）、`hangup` 或 `message` |
| `voicemail_message` | 字符串 | 否 | 在 `voicemail_action="message"` 时使用 |
| `agent_name` | 字符串 | 否 | 上报至控制台和小组件的显示名称 |
| `org_name` | 字符串 | 否 | 用于智能体角色设定的组织显示名称 |
| `tools` | 数组 | 否 | 内联函数工具架构（请参阅[函数工具](/zh/tools/overview)） |
| `transfer_destinations` | 对象数组 \| null | 否 | 此通话最多可转接至 50 个 `{name, number}` 条目，其验证方式与智能体的[转接号码](/api-reference/agents#transfer-numbers)相同。智能体按名称进行转接，且仅可转接至这些号码；`[]` 表示不允许转接。省略此字段或发送 `null`，可让智能体转接至其选择的号码 |
| `timezone` | 字符串 | 否 | 此通话的 IANA 时区，例如 `America/Chicago`。智能体会按该时区说明日期和时间，优先于智能体自身的时区。也可与 `agent_id` 一同使用。非 IANA 时区值将被忽略。请参阅[按通话设置时区](/zh/guides/dynamic-call-config) |
| `call_id` | 整数 | 否 | 请求通话 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>

---

## 包含函数工具的响应

附加工具，以便 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 密钥**
  进行签名。请参阅
  [函数工具](/zh/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/webhooks/call-complete">
    非阻塞式通话结束事件。
  </Card>
  <Card title="函数工具" icon="screwdriver-wrench" href="/zh/tools/overview">
    `tools[]` 的完整 JSON 架构和签名端点契约。
  </Card>
  <Card title="Webhook 端点" icon="bolt" href="/zh/webhooks/endpoints">
    为 `telephony.incoming` / `web.incoming` 订阅多个 URL。
  </Card>
  <Card title="动态通话配置" icon="wand-magic-sparkles" href="/zh/guides/dynamic-call-config">
    用于按来电者设置提示词、工具和 A/B 测试的模式。
  </Card>
</CardGroup>
