Sign inGet Started

在语音序列中使用应用程序数据

当您的应用程序持有通话所需的数据(如订单状态、团队或预约确认)时,使用 voice.webhook。该步骤在 success 上返回这些数据,随后的 Branch 步骤据此选择路径。

定义响应

将 config.output_schema 设置为描述返回数据的对象 schema。该步骤有两个输出:success 携带符合该 schema 的数据,failure 处理技术问题。如果省略配置,输出为空对象。

输入包含静态 HTTPS url 和可选的对象 data。数据支持字面量对象、$literal 或整个对象的 $expr。不支持动态 URL、自定义请求头和凭据。

验证请求

Bird 使用您的工作区回调密钥,通过 Standard Webhooks 请求头为每个请求(包括续接请求)签名:webhook-id、webhook-timestamp 和 webhook-signature。签名覆盖 ID、时间戳和完整的请求体。密钥轮换后的 24 小时内,webhook-signature 为每个有效密钥携带一个 v1, 条目,因此任一密钥都能通过验证。通话可能需要约一分钟才开始使用新密钥签名,因此在重叠期结束前请继续接受旧密钥。您的响应不会被签名:Bird 会验证接收端的 TLS 证书,由 HTTPS 连接对返回内容进行身份验证。

请在解析之前,使用 Standard Webhooks 库的 verify 调用基于原始请求体验证这些请求头。SDK 的 webhooks.unwrap 用于解析平台事件,不适用于这些请求。请先在 Webhooks → Direct callbacks 下轮换密钥,因为初始密钥从不显示。签名表明请求由持有您工作区密钥的一方签署。您的 SMS 回调接收端持有同一密钥,也能签名,因此只在您信任的接收端上保存该密钥。任何能编辑该工作区序列的人都可以选择 URL 和 data。请检查 target_url 是否为您自己的 URL,以拒绝 Bird 为其他接收端签名的请求,并在接收端中对业务操作进行授权。客户授权从不转发。

即使您的工作区强制使用 mTLS,序列 webhook 请求也不会提供客户端证书。请将接收端配置为接受没有客户端证书的连接,并改为验证已签名的请求。

使用已同步的服务器时钟检查已签名的 webhook-timestamp(以秒为单位的 Unix 时间戳)。拒绝早于或晚于当前时间五分钟以上的请求。在执行副作用之前,按 webhook-id 去重。尽可能将 ID 和结果与操作一起原子化存储,并对重复请求返回已保存的结果。每次续接都有自己的 ID。如果不同的请求不得重复执行同一操作,请使用单独的业务幂等键。

返回数据

接收端返回以下封闭的响应结构:

代码示例
{
  "schema_version": 1,
  "type": "outcome",
  "output": { "status": "confirmed", "message": "Your appointment is confirmed." }
}

output 必须符合所配置的 schema。响应返回的是数据,而不是端口、节点 ID 或新命令。续接是在原始 URL 的规范源上的 {"schema_version":1,"type":"continue","url":"https://example.com/voice/next"}。

要根据数据进行路由,请将 success 连接到 logic.branch 步骤,并设置 steps.decision.output.status == 'confirmed' 之类的分支条件。在 schema 中将该字段声明为字符串 enum,这样接收端只能返回这些值。

请求包含运行、会话和通话的标识,已接受的序列和节点的标识,经过求值的 data,以及 results。业务输入请放在 data 中;直接的私密 Gather 结果可能在 results 中出现一次,已完成的录音在可用时带有 ID。这些观察数据不会自动成为表达式字段。

读取接收端请求

草稿测试运行会发送如下结构。下面的 ID 和时间戳均为虚构;实际请求使用已接受的运行和当前节点实例:

代码示例
{
  "schema_version": 1,
  "method": "POST",
  "target_url": "https://example.com/decision",
  "run_id": "vsr_01k5yb7p000000000000000000",
  "sequence": {
    "id": "vsq_01k5yb7p000000000000000000",
    "version_id": null,
    "draft_revision": 1,
    "entry_node_id": "start",
    "mode": "test"
  },
  "session_id": "vcs_01k5yb7p000000000000000000",
  "call": { "id": "vcl_01k5yb7p000000000000000000", "orig": null, "dest": null },
  "node": { "id": "decision", "occurrence_id": "00000000-0000-4000-8000-000000000001" },
  "data": { "appointment_id": "appointment_123" },
  "results": []
}

对于已发布的运行,version_id 标识所选版本,draft_revision 为 null。对于运行了内联定义的通话,id、version_id 和 draft_revision 均为 null。mode 可以是 test 或 production。主叫和被叫使用可为 null 的通话方对象。请先验证签名,再信任这些标识字段。

results 中的 Gather 观察数据包含 type: "gather"、node_id、UUID command_id、digits 和 reason。录音观察数据包含 type: "recording"、UUID command_id、state,以及在可用时的 recording_id。私密按键数字只应用于目标请求;它们不会成为保留的表达式输出。后续请求保持同一节点实例,已传递过的观察数据会从 results 中移除。

处理失败

技术故障样例使用 failure 端口,值为 {"code":"webhook_unavailable"} 或 {"code":"webhook_invalid_response"}。已连接的失败端口会进入恢复路径;未连接时运行失败。未连接的 success 会使运行正常结束。实时 webhook 请求的截止时间为三秒,且不会自动重试。预览不会发送请求。

三秒截止时间包括读取签名密钥的时间。请将 failure 连接到恢复路径,这样当接收端不可用或返回无效响应时,通话仍可继续。超时的请求可能已在接收端产生了副作用。

请将 data、通话标识和 Gather 按键数字视为私密数据。限制对接收端和密钥的访问,避免在日志中记录请求体或密钥,并且只在应用需要期间保留来电者输入。不要在 URL 中放入私密数据或凭据:通话追踪会保留完整 URL,包括查询字符串。编写的 URL 和数据以及经过验证的输出会保留在运行历史中;临时的 Gather 按键数字不会记录在步骤输入中。只返回序列需要的数据。

在 steps.decision.output 读取返回的数据;在 steps.decision.error.code 读取技术错误。如果两条路径汇合,请先用 has(steps.decision.output) 或 has(steps.decision.error) 检查包装对象,再使用它。接收端返回 HTTP 200 并不代表结果有效,其响应体还必须符合约定。

继续查看此主题的文档、指南和示例。