← 所有文章
教程

如何在客服系统中接入 WhatsApp 语音通话:从创建链接到接通回调

在客服系统中接入 UnifyPort WhatsApp 语音通话,需要先由后端使用 media_type: "voice" 准备通话,再把私密浏览器链接交给负责的客服。客服打开链接并点击 Start call 后才拨号。后端将返回的 call_id 与客服任务关联,通过通话事件跟进结果。

本文面向内测中的人工发起、单聊呼出通话。UnifyPort 使用非官方接口,这不是 Meta WhatsApp Business Calling API 的接入教程。功能概览与申请方式见通话内测公告。

1. 准备消息账号并选定会话

先联系技术支持申请工作区通话资格。创建 API Key 不会自动开通;is_beta 是只读资格标识,不能通过修改工作区 metadata 自行开启。

展示通话入口前,确认 WhatsApp 消息账号已授权,且 runtime_status 为 running。从同一账号下选择私聊会话,保留接口返回的 conversation_id,不要替换为手机号或自行拼接渠道标识。

在应用中,将选中的工作区、账号、会话和客服任务绑定到本次操作,并由后端校验操作人的权限。新消息到达或客服切换会话时,不能悄悄改变已经准备好的通话目标。

下文中的 API Key 始终保存在服务端,浏览器只接收本次需要的私密通话链接。

2. 后端准备语音通话

将占位符替换为工作区 API Key、已连接账号和选中的会话,在服务端执行:

curl -X POST 'https://api.unifyport.ai/v1/calls' \
  -H 'X-Api-Key: <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "account_id": "acc_example",
    "to": { "id": "<conversation_id>", "type": "user" },
    "media_type": "voice"
  }'

创建通话文档规定成功返回 201 Created,响应结构如下。这里的值只是示例,不是真实通话回执:

{
  "request_id": "req_example",
  "data": {
    "call_id": "call_example",
    "direction": "outbound",
    "media_type": "voice",
    "state": "preparing",
    "join_url": "https://calls.example.com/call/call_example#ticket=example-only",
    "expires_at": "2026-10-27T08:02:00Z"
  }
}

将 data.call_id 与选中的账号、会话及任务一起保存,排查问题时保留 request_id。把 data.join_url 当作秘密:原样交给负责本次通话的客服,完整链接不得进入日志、分析埋点或公开消息。

201 和 preparing 只表示链接准备好了,不代表客户手机已经响铃,也不代表客户已经接听。

3. 由客服在浏览器中开始通话

在业务界面展示选中的客户和“可发起通话”提示,只向本次操作人提供 join_url。客服在通话页允许麦克风访问,并在 expires_at 前点击 Start call。

准备期从创建起默认两分钟,服务端可以调整,因此要读取 expires_at,不要自行写死截止时间。准备期限不限制已接通通话的时长。麦克风权限必需,摄像头权限可选。

等待创建响应期间,禁用重复提交。若响应丢失,先显示结果待确认并查询当前通话,再考虑是否重新创建。只禁用按钮无法协调多个客服,后端也需要处理账号级通话冲突。

4. 用 Webhook 关联客服任务

如果 Webhook 端点采用具体事件列表,应在保留仍需订阅的其他事件的同时加入下面两个名称。这只是字段片段,不是完整的端点创建请求:

{
  "subscribed_events": ["call.answered", "call.ended"]
}

内测工作区使用 *、空列表或省略列表时,也包含有权接收的通话事件。非内测工作区不会因为使用通配订阅而获得通话事件权限。

观察到的结果应用中的处理
创建响应中的 data.call_id与选中的客服任务建立关联
call.answered 中的 data.call.id记录这次通话已接通
call.ended 中的 data.call.id、data.reason、data.duration_seconds记录结束原因和接通时长

处理签名事件前,先为端点配置 signing_secret,并将签名密钥保存在服务端。

按 Webhook 投递契约,使用原始请求体校验签名,按事件信封中的 id 去重。用事件的 account_id 和 data.call.id 匹配已保存的关联。处理延迟或重复事件时,不要让已结束通话在界面中重新变成进行中。

单独保留“已接通”的证据:duration_seconds 向下取整,因此 0 本身不能证明未接通。创建阶段同步失败可能没有结束事件,HTTP 错误需要独立记录。完整载荷见接通事件和结束事件文档。通话结束也不自动代表客服任务已解决。

5. 查询或结束当前受管理通话

恢复状态或丢失通话标识时,使用查询当前通话接口:

curl --get 'https://api.unifyport.ai/v1/calls/active' \
  -H 'X-Api-Key: <YOUR_API_KEY>' \
  --data-urlencode 'account_id=acc_example'

data: {} 表示该账号当前没有 UnifyPort 管理的通话。它不是通话历史,不能证明上一次尝试的结果,也不能检测 WhatsApp 原生应用中的通话。该响应不会重新签发一次性 join_url。

操作人决定结束一通已知通话时,使用保存的 call_id 及其所属账号:

curl -X POST 'https://api.unifyport.ai/v1/calls/call_example/hangup' \
  -H 'X-Api-Key: <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"account_id":"acc_example"}'

挂断接口返回 ended 或 failed 终态;重复挂断可能返回已有终态。不要仅因状态为 failed 就继续挂断,也不要为了消除创建冲突而结束其他客服的通话。

常见接入问题

情况下一步
403 beta_feature_not_enabled联系支持核对工作区资格,修改 metadata 无法开通
准备链接过期停止复用;检查当前通话后,由客服明确决定是否重新准备
浏览器无法使用麦克风先检查通话页的麦克风权限,再判断是否能正常拨号
创建请求与已有通话冲突查询受管理通话,在业务系统中确认归属客服和用途
创建或挂断请求超时保持结果待确认,结合当前状态与已收到事件核对后再决定是否重试

验收一条完整客服流程

在获得同意的测试会话中,检查正常接通与挂断、准备链接过期、拒绝麦克风权限、重复点击、账号冲突和 HTTP 响应丢失。确认重复投递不会产生重复任务记录,完整通话链接不会进入日志。这些是你接入时应执行的验收项,不是本文宣称已经完成的实测结果。

可以自动拨打一批手机号吗?

不可以。本流程需要账号下的 conversation_id,并由人工在浏览器中点击 Start call。

同一套接入可以接听客户来电吗?

当前内测不可以。call.incoming 是预留契约,出现在事件目录中不代表开放呼入。群通话、录音、转写、SIP/PBX 集成和 AI 语音客服也不属于本次内测范围。

团队应从哪里开始?

尚未开通资格时,可携工作区 ID 和计划使用的客服场景联系技术支持申请,不要发送 API Key 或私密通话链接。开通后,先按创建通话文档验证一个明确选中的会话,再把入口接入客服流程。

UnifyPort API

让消息接入变成一条稳定的产品管线。

先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。