如何在客服系统中接入 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 或私密通话链接。开通后,先按创建通话文档验证一个明确选中的会话,再把入口接入客服流程。
让消息接入变成一条稳定的产品管线。
先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。