Open in
Webhook 概览
了解 ThunderPhone 如何传递实时事件、如何验证签名,以及旧版与基于端点的传递模型之间的比较。
ThunderPhone 会在通话期间发生事件时向您的服务器发送 HTTP POST 请求——例如呼入通话开始、通话结束、评分运行完成、警报触发等。提供两种交付模式:
支持多个 URL、每个端点独立的密钥、每个端点独立的事件筛选器,以及自动重试。
通过 GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints 管理。
每个组织一个 URL。承载通话生命周期事件,包括阻塞式配置交换。通过 GET/PUT /v1/webhook 管理。
事件目录中列出的所有事件类型都会通过 Webhook 端点交付。六种通话生命周期事件(telephony.incoming、telephony.complete、telephony.tool、web.incoming、web.complete、web.tool)也会发送到旧版单 URL Webhook——如果您同时配置了旧版 URL 和匹配的端点,您会在两条路径上收到该事件。阻塞行为(telephony.incoming / web.incoming 配置交换以及 Webhook 模式的工具分派)仅存在于旧版路径;每次端点交付都是即发即弃的通知。
负载格式
端点交付的数据是一个包含 data、event_id 和 type 的 JSON 对象:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id 对每个发出的事件都是唯一的。它在重试期间以及在接收该事件的每个端点之间均保持一致——请据此去重。
旧版单 URL Webhook 发送相同的 type 和 data,但不包含 event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}在线路传输中,每个请求体都会以规范化方式序列化——键按字母顺序排序、无空白字符、使用 UTF-8。这些文档中的美化示例仅用于提升可读性。
请参阅事件目录,了解完整的事件类型和负载字段列表。
签名验证
每个请求都带有两个签名,二者均使用端点的 secret(对于旧版投递,则使用您的组织级 webhook secret)作为密钥,通过 HMAC-SHA256 生成:
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>对时间戳、方法、路径和查询参数以及原始请求体进行签名。请验证此签名,并拒绝t与您的时钟相差超过 5 分钟 的请求,以防截获的请求随后被重放。重试请求会在发送时重新签名。X-ThunderPhone-Signature仅对原始请求体进行签名。该签名仍会原样发送,以确保现有验证器能够继续正常工作。
步骤
- 在进行任何解析之前,读取请求头、方法、原始路径和查询参数,以及原始请求体。
- 使用您的密钥计算
<t>\n<METHOD>\n<path and query>\n<body>的十六进制 HMAC-SHA256。 - 如果
t的偏差超过 300 秒,或结果在恒定时间比较中与v1不匹配,则拒绝该请求。
请求体采用规范化 JSON 序列化格式(键已排序、分隔符紧凑),并且我们签名的字节与传输的字节完全一致。验证指南提供了精确的签名字符串、完整示例、不使用 SDK 的代码以及旧版请求头。
from flask import Flask, abort, request
from thunderphone import verify_signature
app = Flask(__name__)
@app.post("/thunderphone-webhook")
def handle():
query = request.query_string.decode()
if not verify_signature(
request.headers.get("X-ThunderPhone-Signature-V2"),
secret=WEBHOOK_SECRET,
method=request.method,
path=request.path + (f"?{query}" if query else ""),
body=request.get_data(), # raw bytes
):
abort(401)
event = request.get_json()
# dispatch on event["type"] …
return "", 204import express from "express";
import { verifySignature } from "@thunderphone/sdk";
const app = express();
app.post(
"/thunderphone-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const ok = verifySignature({
header: req.header("X-ThunderPhone-Signature-V2"),
secret: process.env.WEBHOOK_SECRET,
method: req.method,
path: req.originalUrl,
body: req.body,
});
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
// dispatch on event.type …
res.sendStatus(204);
},
);投递语义
这些语义适用于 端点 投递。旧版单 URL
webhook 仅进行一次同步尝试且不重试,但电话
telephony.incoming
配置交换除外,该交换会重试一次。
重试
每个事件都会立即尝试投递一次。任何 2xx 响应
都会确认投递。对于任何其他结果(非 2xx、
连接错误、超时),我们会在首次尝试后的 1 分钟、5 分钟、30 分钟、2 小时、6 小时、
12 小时和 24 小时进行重试——共 8 次尝试,持续
24 小时。如果每次尝试都失败,投递将停止,端点
会在webhook 端点中标记为
status="failing";我们会向您组织的
所有者和管理员发送电子邮件,并在控制台中显示该状态。请在
有效负载被持久化接收后尽快返回 2xx;请异步处理。
来自端点测试 API 的测试发送会在首次尝试约一分钟后重试一次,
且绝不会更改端点状态。
顺序
投递顺序采用尽力而为的方式。实际上,我们会按照事件发出的顺序进行投递,但失败后的重试可能会改变顺序。
请始终按 call_id / 对象 ID 去重并进行协调。
重复
投递为至少一次:在我们未收到响应后进行重试时,可能会重复投递事件。每次重试都会携带相同的
event_id,因此请存储已处理的 ID 并跳过重复项。event_id 也会在端点之间共享——订阅同一事件的两个端点会收到相同的 event_id。
超时
端点投递每次尝试的超时为 30 秒。在
旧版路径中,驱动实时通话行为的阻塞请求——
telephony.incoming / web.incoming
配置交换——电话首次尝试的时间限制为 10 秒,
整个交换过程(包括其一次
重试)限制为 13 秒,而小组件会话限制为 10 秒。响应缓慢会延迟
电话接听,因此请尽量在几秒内响应。Webhook 模式的工具调度
默认允许 20 秒,工具声明可以设置顶层 timeout。
源 IP
出站 Webhook 源自 ThunderPhone 的云 IP 范围。 如果您的防火墙需要允许列表,请联系支持团队,我们将提供当前范围。
在传统 Webhook 与基于端点的 Webhook 之间选择
| 功能 | 旧版(/v1/webhook) | 端点(/v1/developer/webhook-endpoints) |
|---|---|---|
| URL 数量 | 每个组织 1 个 | 每个组织多个 |
| 事件覆盖范围 | 仅 telephony.* / web.* | 全部 10 种事件类型 |
| 事件筛选 | —— | 按端点筛选 |
| 重试 | 无,电话通话配置交换除外,可重试 1 次 | 24 小时内尝试 8 次 |
| 信封格式 | type + data | type + data + event_id |
| 密钥轮换 | 替换单个密钥 | 每个端点独立密钥 |
| 不删除即可停用 | 使用 PUT /v1/webhook 并传入 {"url": ""} | status=disabled |
| 状态可见性 | —— | active / disabled / failing |
| 阻塞式配置交换 | 是(telephony.incoming / web.incoming) | 从不——仅通知 |
| 最适用于 | 动态通话配置 | 生产环境中的事件消费 |
新的集成应通过基于端点的 Webhook 接收事件。仅当您需要在接听时动态配置通话,或使用 Webhook 模式的工具调度时,才应保留(或添加)传统 URL——这些请求/响应交换仅在传统路径上运行。