集成业务服务端
微信 VoIP 通话涉及小程序、业务服务端、探鸽云平台、设备端四方交互。业务服务端是自研核心:对接微信回调、调用探鸽云接口、通过长连接向设备下发消息、面向小程序和设备提供业务 API。
接口归属说明:
- 微信提供:
cgi-bin/token、sns/jscode2session、wxa/getsnticket、wxa/business/iot/voip/call等服务端 API,以及iot_voip_notify回调推送- 探鸽云提供:
POST /v1/token/wxvoip(TGV1-HMAC-SHA256 鉴权)- 业务服务端自行实现:所有
/v1/voip/*路径的 HTTP 接口、微信回调端点、设备下行通道
文中 API 路径与字段名称为参考示例,实际开发可按团队规范自行定义,只需保证职责边界一致。
阅读本文前,请先了解 通话流程。
一、需要实现什么
按开发顺序,业务服务端需要依次完成以下模块:
| 顺序 | 模块 | 做什么 |
|---|---|---|
| 1 | 微信 API 封装 | 封装 access_token 获取与缓存,后续所有微信接口依赖它 |
| 2 | 微信回调处理 | 接收 join_voip_room → 调探鸽云 Token → 推送下行消息至设备 |
| 3 | 设备下行通道 | 服务端到设备的长连接推送通道,下呼叫通知、取消通知、授权变更通知 |
| 4 | 小程序侧 API | 微信登录、VoIP 授权绑定/解绑、SN Ticket 签发 |
| 5 | 设备侧 API | 设备上报 media profile、查询授权列表、主动发起呼叫 |
| 6 | 取消呼叫 | 小程序侧 API + 下行 call_cancel |
二、微信 API 封装
所有微信接口依赖 access_token,应先封装好。
2.1 access_token
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}
- 有效期 7200s,内存缓存,提前 5 分钟刷新
- 同一 appID 并发只需一次 HTTP 请求
2.2 jscode2session
GET https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code
小程序 wx.login() → code → 服务端换取 openid。
2.3 getsnticket
POST https://api.weixin.qq.com/wxa/getsnticket?access_token={access_token}
{ "sn": "TIRZ00000001", "model_id": "HRHY_xxx" }返回 sn_ticket,供小程序 wx.requestDeviceVoIP 使用。
2.4 iot/voip/call
POST https://api.weixin.qq.com/wxa/business/iot/voip/call?access_token={access_token}
{
"model_id": "HRHY_xxx",
"sn": "TIRZ00000001",
"openid": "o4DLd5...",
"room_type": "voice",
"version_type": 2,
"payload": ""
}调用后微信向目标 openid 的小程序推送来电振铃。
三、接收微信服务器回调
这是业务服务端的核心链路。拿到 access_token 后优先对接。
3.1 端点
GET|POST /v1/voip/notification/:wx_app_id
在微信公众平台「基本设置 → 服务器配置」中将 URL 设为:
https://your-domain.com/v1/voip/notification/{wx_app_id}3.2 处理流程
POST /v1/voip/notification/:wx_app_id
│
├─ ① 校验签名
│ sha1(sort(token, timestamp, nonce)) == query 参数 signature
│ 安全模式(encrypt_type=aes)还需校验 msg_signature
│
├─ ② AES 解密(若 encrypt_type=aes)
│ EncodingAESKey(43 字符 Base64)→ 32 字节密钥
│ 解密后格式:random(16) + msg_len(4) + XML + app_id
│ 校验尾部 app_id 与配置一致,否则拒绝
│
├─ ③ 解析 XML,仅处理:
│ MsgType = "event"
│ Event = "iot_voip_notify"
│ Action = "join_voip_room"
│ 其余返回 errcode 非 0
│
├─ ④ 用 Sn 查询该设备的 media profile(设备通过 /v1/voip/device/profile 上报)
│ 若无 profile → 返回 errcode=10,无法下发呼叫
│
├─ ⑤ 调用探鸽云 POST /v1/token/wxvoip(详见第四节)
│ 入参 = 微信回调字段(透传)+ profile 字段 + 服务端配置字段
│ 回参 = peer_id + token
│
├─ ⑥ 通过下行通道推送 call_incoming 至设备(详见第五节)
│
└─ ⑦ 返回微信 {"errcode":0, "errmsg":"ok"}3.3 字段流转一览
下表中「→ Token」表示需传入探鸽云 Token 请求,「→ 下行」表示需放入 call_incoming 消息。
| 来源 | 字段 | → Token | → 下行 | 说明 |
|---|---|---|---|---|
| 微信回调 XML | Sn | ✓ | — | 设备标识,查 profile 用 |
| 微信回调 XML | RoomId | ✓ | ✓ | 作为 wx_room_id |
| 微信回调 XML | SessionKey | ✓ | ✓ | 作为 wx_session_key |
| 微信回调 XML | ServerToken | ✓ | ✓ | 作为 wx_server_token |
| 微信回调 XML | Payload | ✓ | ✓ | Base64 原文透传 |
| 微信回调 URL | openid | — | ✓ | 呼叫方身份 |
| 设备 profile | 全部字段 | ✓ | — | 音视频参数 |
| 服务端配置 | wx_app_id | ✓ | ✓ | |
| 服务端配置 | wx_model_id | ✓ | ✓ | |
| 探鸽云响应 | peer_id | — | ✓ | 设备 WHIP 连接地址 |
| 探鸽云响应 | token | — | ✓ | 设备 WHIP 鉴权凭证 |
核心原则:微信回调字段几乎全部透传,服务端不做语义解析,只做搬运和组装。
四、调用探鸽云 Token 接口
POST /v1/token/wxvoip — TGV1-HMAC-SHA256 鉴权,完整字段见接口文档。
每次收到微信 join_voip_room 回调时调用,将微信会话字段与设备能力合并,换取设备所需的 WHIP 连接凭证。
请求体:
{
"wx_session_key": "<微信回调透传>",
"wx_room_id": "<微信回调透传>",
"wx_session_token": "<微信回调透传>",
"wx_app_id": "<服务端配置>",
"device_id": "<微信回调 Sn>",
"wx_payload": "<微信回调 Payload 原文>",
"wx_model_id": "<服务端配置>",
"calling_timeout_sec": "<设备 profile>",
"no_video": "<设备 profile>",
"video_mt": "<设备 profile>",
"screen_width": "<设备 profile>",
"screen_height": "<设备 profile>",
"audio_rate": "<设备 profile>",
"audio_channels": "<设备 profile>"
}响应:
{ "code": 0, "data": { "peer_id": "whips://wxvoip?...", "token": "v1.eyJ..." } }peer_id 和 token 不做解析,原样放入下行 call_incoming 下发。
五、设备下行通道
业务服务端需要一条到设备的长连接通道,用于主动推送消息。技术选型不限(MQTT、WebSocket、TCP 自定义协议等),只需满足:服务端知道设备在线时能寻址到具体设备并投递消息。
以下描述的是消息约定,与具体通道无关。以 MQTT 为例:服务端作为 MQTT Client 接入 Broker,按 device/sn_{device_id}/cmd 发指令、device/sn_{device_id}/notify 发通知。换成 WebSocket 或自定义 TCP 协议同理,只需保证消息能路由到目标设备。
5.1 消息信封
所有下行消息使用相同的 JSON 结构:
{
"type": "<消息类型>",
"channel": "wx",
"payload": { ... }
}5.2 消息分类
消息分为两类:
| 类别 | 说明 | 需要设备确认 |
|---|---|---|
| 指令(cmd) | 关键操作,如来电通知。设备收到后须回复确认 | 是 |
| 通知(notify) | 告知性消息,如取消呼叫、授权变更 | 否 |
如何区分两类消息取决于通道实现。MQTT 可用不同 topic(/cmd vs /notify),WebSocket 可在信封中加 "category": "cmd" 字段。
5.3 call_incoming — 来电通知
指令类消息。微信回调处理完毕后推送。
{
"type": "call_incoming",
"channel": "wx",
"payload": {
"peer_id": "whips://wxvoip?...",
"token": "v1.eyJ...",
"wx_app_id": "wx27d4b2d7eb37eb58",
"wx_model_id": "HRHY_vJ9mHI2KQhd6yvj9Q",
"wx_room_id": "wxf830863...",
"wx_user_openid": "o4DLd5...",
"wx_user_nickname": "",
"wx_server_token": "...",
"wx_session_key": "...",
"wx_payload": "...",
"wx_call_id": "...",
"wx_from": "..."
}
}各字段来源:
| 字段 | 来源 | 设备用途 |
|---|---|---|
peer_id | 探鸽云 Token 接口响应 | TiRtcWhipConnect 第一参数 |
token | 探鸽云 Token 接口响应 | TiRtcWhipConnect 第二参数 |
wx_app_id | 服务端配置 | 标识来源小程序 |
wx_model_id | 服务端配置 | 标识 IoT 型号 |
wx_room_id | 微信回调 RoomId 透传 | 挂断/拒接时需回传 |
wx_user_openid | 微信回调 URL query openid 透传 | 呼叫方身份 |
wx_server_token | 微信回调 ServerToken 透传 | 拒接时设备传给 TiRtcServiceRequest |
wx_session_key | 微信回调 SessionKey 透传 | 会话密钥 |
wx_payload | 微信回调 Payload 透传 | 自定义数据,Base64 原文 |
wx_call_id | Payload 解码 .id 透传 | 通话业务标识 |
wx_from | Payload 解码 .from 透传 | 主叫方:"miniapp" 或 "device" |
5.4 call_cancel — 取消呼叫
通知类消息。
{ "type": "call_cancel", "channel": "wx", "payload": { "wx_room_id": "wxf830863..." } }触发时机:小程序调 /v1/voip/user/cancel。设备根据 wx_room_id 匹配当前通话,结束振铃或断开连接。
5.5 callers_update — 授权变更
通知类消息。
{ "type": "callers_update", "channel": "wx", "payload": {} }触发时机:用户上报或删除授权。设备收到后重新调 GET /v1/voip/device/callers 刷新本地列表。
六、面向小程序的 API
小程序通过 HTTPS 调用,鉴权方式由业务方自定。
6.1 微信登录
POST /v1/voip/user/wechat-mini-login
// 请求
{ "code": "wx_login_code" }
// 响应
{ "code": 0, "data": { "wx_user_openid": "o4DLd5..." } }内部调微信 jscode2session。
6.2 VoIP 授权绑定
POST /v1/voip/user/report-auth
小程序完成 wx.requestDeviceVoIP 授权后调用。
// 请求
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5...", "wx_model_id": "HRHY_xxx" }
// 响应
{ "code": 0 }成功后存储授权关系,同时通过下行通道推送 callers_update 通知设备。
6.3 删除授权
POST /v1/voip/user/delete-auth
// 请求
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5..." }
// 响应
{ "code": 0 }6.4 SN Ticket
POST /v1/voip/user/sn-ticket
// 请求
{ "device_id": "TIRZ00000001" }
// 响应
{ "code": 0, "data": { "sn_ticket": "..." } }内部调微信 getsnticket。
6.5 取消呼叫
POST /v1/voip/user/cancel
// 请求
{ "device_id": "TIRZ00000001", "wx_room_id": "wxf830863..." }
// 响应
{ "code": 0 }通过下行通道推送 call_cancel 至设备。仅用于小程序主叫且设备未接听时。
七、面向设备的 API
设备通过 HTTPS 调用,鉴权方式由业务方自定(如 JWT)。
7.1 上报媒体能力
POST /v1/voip/device/profile
必须在上线时调用,否则微信回调到达时服务端无法获取设备能力,呼叫通知将下发失败。
// 请求
{
"screen_width": 0,
"screen_height": 0,
"audio_rate": 8000,
"audio_channels": 1,
"video_mt": "",
"no_video": true,
"calling_timeout_sec": 30
}
// 响应
{ "code": 0 }这些字段最终组装到探鸽云 Token 请求 中,各参数的取值范围以 Token 接口为准:
| profile 字段 | 对应 Token 字段 | 允许值(详见 服务端接口) |
|---|---|---|
audio_rate | audio_rate | 8000 / 16000 |
audio_channels | audio_channels | 1 / 2 |
video_mt | video_mt | h264 / mjpeg / none |
no_video | no_video | 纯音频设备传 true |
screen_width/height | screen_width / screen_height | 非纯音频时必填 |
calling_timeout_sec | calling_timeout_sec | 振铃超时秒数,默认 30 |
若需要更精细的方向控制,可在 profile 中补充 up_video_mt(h264/h265/mjpeg/none)、down_video_mt(h264/mjpeg/none)、down_audio_mt(alaw/amr/opus)等字段,服务端透传至 Token 请求即可。注意 video_mt 与 up_video_mt/down_video_mt 不可同时使用。
设备每次上线更新。服务端存储后,在收到微信回调时取出组装到 Token 请求中。
7.2 查询授权用户列表
GET /v1/voip/device/callers
// 响应
{ "code": 0, "data": { "list": [
{ "wx_open_id": "o4DLd5...", "wx_app_id": "wx27d...", "wx_model_id": "HRHY...", "created_at": "..." }
] } }设备主动呼叫前从此列表选取目标用户。
7.3 设备主动呼叫小程序
POST /v1/voip/device/call
// 请求
{ "device_id": "TIRZ00000001", "wx_user_openid": "o4DLd5...", "wx_room_type": "voice" }
// 响应
{ "code": 0 }内部调微信 iot/voip/call。调用后:
- 微信向小程序推送来电振铃
- 用户接听 → 微信回调
join_voip_room - 后续流程与小程序主叫完全相同
八、双向呼叫的差异
两种场景的差异仅在于呼叫由谁发起。
场景一:小程序呼叫设备
小程序 VoIP 插件 ──▶ 微信服务器 ── join_voip_room ──▶ 业务服务端
│
① 验签/解密
② 查 profile
③ 调探鸽云 Token
④ 下行 call_incoming ──▶ 设备
│
⑤ TiRtcWhipConnect
⑥ 收到 cmdw=0x2000(双方对讲建立成功)服务端角色:纯被动 —— 接收微信回调,转发设备。
场景二:设备呼叫小程序
设备 ── POST /v1/voip/device/call ──▶ 业务服务端 ── 微信 iot/voip/call ──▶ 微信服务器
│
小程序振铃
用户接听
│
业务服务端 ◀── join_voip_room ──── 微信服务器
│
① 验签/解密
② 查 profile
③ 调探鸽云 Token
④ 下行 call_incoming ──▶ 设备
│
⑤ TiRtcWhipConnect
⑥ 收到 cmdw=0x2000(双方对讲建立成功)服务端角色:先主动调微信 API 发起呼叫,再被动接收回调处理 —— 回调处理逻辑与场景一完全相同。
差异对照
| 场景一(小程序主叫) | 场景二(设备主叫) | |
|---|---|---|
| 呼叫发起 | 小程序 VoIP 插件 | 设备调 POST /v1/voip/device/call |
| 服务端额外步骤 | 无 | 调微信 iot/voip/call |
join_voip_room 处理 | 相同 | 相同 |
| 探鸽云 Token | 相同 | 相同 |
| 下行推送 | 相同 | 相同 |
| cmdw=0x2000 | WhipConnect 成功后收到,双方对讲建立成功 | 相同 |