Skip to content

集成业务服务端

微信 VoIP 通话涉及小程序、业务服务端、探鸽云平台、设备端四方交互。业务服务端是自研核心:对接微信回调、调用探鸽云接口、通过长连接向设备下发消息、面向小程序和设备提供业务 API。

接口归属说明

  • 微信提供:cgi-bin/tokensns/jscode2sessionwxa/getsnticketwxa/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}

json
{ "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}

json
{
  "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→ 下行说明
微信回调 XMLSn设备标识,查 profile 用
微信回调 XMLRoomId作为 wx_room_id
微信回调 XMLSessionKey作为 wx_session_key
微信回调 XMLServerToken作为 wx_server_token
微信回调 XMLPayloadBase64 原文透传
微信回调 URLopenid呼叫方身份
设备 profile全部字段音视频参数
服务端配置wx_app_id
服务端配置wx_model_id
探鸽云响应peer_id设备 WHIP 连接地址
探鸽云响应token设备 WHIP 鉴权凭证

核心原则:微信回调字段几乎全部透传,服务端不做语义解析,只做搬运和组装


四、调用探鸽云 Token 接口

POST /v1/token/wxvoipTGV1-HMAC-SHA256 鉴权,完整字段见接口文档。

每次收到微信 join_voip_room 回调时调用,将微信会话字段与设备能力合并,换取设备所需的 WHIP 连接凭证。

请求体:

json
{
  "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>"
}

响应:

json
{ "code": 0, "data": { "peer_id": "whips://wxvoip?...", "token": "v1.eyJ..." } }

peer_idtoken 不做解析,原样放入下行 call_incoming 下发。


五、设备下行通道

业务服务端需要一条到设备的长连接通道,用于主动推送消息。技术选型不限(MQTT、WebSocket、TCP 自定义协议等),只需满足:服务端知道设备在线时能寻址到具体设备并投递消息

以下描述的是消息约定,与具体通道无关。以 MQTT 为例:服务端作为 MQTT Client 接入 Broker,按 device/sn_{device_id}/cmd 发指令、device/sn_{device_id}/notify 发通知。换成 WebSocket 或自定义 TCP 协议同理,只需保证消息能路由到目标设备。

5.1 消息信封

所有下行消息使用相同的 JSON 结构:

json
{
  "type":    "<消息类型>",
  "channel": "wx",
  "payload": { ... }
}

5.2 消息分类

消息分为两类:

类别说明需要设备确认
指令(cmd)关键操作,如来电通知。设备收到后须回复确认
通知(notify)告知性消息,如取消呼叫、授权变更

如何区分两类消息取决于通道实现。MQTT 可用不同 topic(/cmd vs /notify),WebSocket 可在信封中加 "category": "cmd" 字段。

5.3 call_incoming — 来电通知

指令类消息。微信回调处理完毕后推送。

json
{
  "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_idPayload 解码 .id 透传通话业务标识
wx_fromPayload 解码 .from 透传主叫方:"miniapp" 或 "device"

5.4 call_cancel — 取消呼叫

通知类消息。

json
{ "type": "call_cancel", "channel": "wx", "payload": { "wx_room_id": "wxf830863..." } }

触发时机:小程序调 /v1/voip/user/cancel。设备根据 wx_room_id 匹配当前通话,结束振铃或断开连接。

5.5 callers_update — 授权变更

通知类消息。

json
{ "type": "callers_update", "channel": "wx", "payload": {} }

触发时机:用户上报或删除授权。设备收到后重新调 GET /v1/voip/device/callers 刷新本地列表。


六、面向小程序的 API

小程序通过 HTTPS 调用,鉴权方式由业务方自定。

6.1 微信登录

POST /v1/voip/user/wechat-mini-login

json
// 请求
{ "code": "wx_login_code" }
// 响应
{ "code": 0, "data": { "wx_user_openid": "o4DLd5..." } }

内部调微信 jscode2session

6.2 VoIP 授权绑定

POST /v1/voip/user/report-auth

小程序完成 wx.requestDeviceVoIP 授权后调用。

json
// 请求
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5...", "wx_model_id": "HRHY_xxx" }
// 响应
{ "code": 0 }

成功后存储授权关系,同时通过下行通道推送 callers_update 通知设备。

6.3 删除授权

POST /v1/voip/user/delete-auth

json
// 请求
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5..." }
// 响应
{ "code": 0 }

6.4 SN Ticket

POST /v1/voip/user/sn-ticket

json
// 请求
{ "device_id": "TIRZ00000001" }
// 响应
{ "code": 0, "data": { "sn_ticket": "..." } }

内部调微信 getsnticket

6.5 取消呼叫

POST /v1/voip/user/cancel

json
// 请求
{ "device_id": "TIRZ00000001", "wx_room_id": "wxf830863..." }
// 响应
{ "code": 0 }

通过下行通道推送 call_cancel 至设备。仅用于小程序主叫且设备未接听时。


七、面向设备的 API

设备通过 HTTPS 调用,鉴权方式由业务方自定(如 JWT)。

7.1 上报媒体能力

POST /v1/voip/device/profile

必须在上线时调用,否则微信回调到达时服务端无法获取设备能力,呼叫通知将下发失败。

json
// 请求
{
  "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_rateaudio_rate8000 / 16000
audio_channelsaudio_channels1 / 2
video_mtvideo_mth264 / mjpeg / none
no_videono_video纯音频设备传 true
screen_width/heightscreen_width / screen_height非纯音频时必填
calling_timeout_seccalling_timeout_sec振铃超时秒数,默认 30

若需要更精细的方向控制,可在 profile 中补充 up_video_mth264/h265/mjpeg/none)、down_video_mth264/mjpeg/none)、down_audio_mtalaw/amr/opus)等字段,服务端透传至 Token 请求即可。注意 video_mtup_video_mt/down_video_mt 不可同时使用。

设备每次上线更新。服务端存储后,在收到微信回调时取出组装到 Token 请求中。

7.2 查询授权用户列表

GET /v1/voip/device/callers

json
// 响应
{ "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

json
// 请求
{ "device_id": "TIRZ00000001", "wx_user_openid": "o4DLd5...", "wx_room_type": "voice" }
// 响应
{ "code": 0 }

内部调微信 iot/voip/call。调用后:

  1. 微信向小程序推送来电振铃
  2. 用户接听 → 微信回调 join_voip_room
  3. 后续流程与小程序主叫完全相同

八、双向呼叫的差异

两种场景的差异仅在于呼叫由谁发起

场景一:小程序呼叫设备

小程序 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=0x2000WhipConnect 成功后收到,双方对讲建立成功相同

微信VoIP通话