Skip to content

Business Server Integration

WeChat VoIP calling involves four parties: Mini Program, business server, Tange Cloud platform, and device. The business server is the custom core: it handles WeChat callbacks, calls Tange Cloud APIs, pushes messages to devices over a long-connection channel, and exposes APIs for both the Mini Program and devices.

Interface ownership:

  • WeChat provides: cgi-bin/token, sns/jscode2session, wxa/getsnticket, wxa/business/iot/voip/call server APIs, and iot_voip_notify callback push
  • Tange Cloud provides: POST /v1/token/wxvoip (TGV1-HMAC-SHA256 authentication)
  • Business server implements: all /v1/voip/* HTTP endpoints, the WeChat callback endpoint, and the device downlink channel

The API paths and field names in this document are reference examples. Use your own naming conventions in production — only the responsibilities need to match.

Read Call flows first.


I. What to Implement

In development order:

#ModulePurpose
1WeChat API wrapperEncapsulate access_token retrieval and caching — all other WeChat APIs depend on it
2WeChat callback handlingReceive join_voip_room → call Tange Cloud Token API → push downlink message to device
3Device downlink channelServer-to-device push channel for call notifications, cancel, and authorization updates
4Mini Program APIsWeChat login, VoIP authorization bind/unbind, SN Ticket issuance
5Device APIsDevice media profile upload, authorized caller list, active call initiation
6Call cancellationMini Program API + downlink call_cancel

II. WeChat API Wrapper

All WeChat APIs require access_token. Wrap this first.

2.1 access_token

GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}

  • Valid for 7200s. Cache in memory with a 5-minute refresh margin.
  • Use per-appID mutex to avoid duplicate requests.

2.2 jscode2session

GET https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code

Mini Program calls wx.login() → code → server exchanges for openid.

2.3 getsnticket

POST https://api.weixin.qq.com/wxa/getsnticket?access_token={access_token}

json
{ "sn": "TIRZ00000001", "model_id": "HRHY_xxx" }

Returns sn_ticket for the Mini Program's wx.requestDeviceVoIP call.

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":   ""
}

Calling this triggers an incoming call ring on the target user's Mini Program.


III. Handling WeChat Server Callbacks

This is the core processing chain. Prioritize this after obtaining access_token.

3.1 Endpoint

GET|POST /v1/voip/notification/:wx_app_id

Configure this URL in the WeChat Official Accounts admin panel:

https://your-domain.com/v1/voip/notification/{wx_app_id}

3.2 Processing Flow

POST /v1/voip/notification/:wx_app_id

  ├─ ① Verify signature
  │     sha1(sort(token, timestamp, nonce)) == query param signature
  │     In secure mode (encrypt_type=aes), also verify msg_signature

  ├─ ② AES decrypt (if encrypt_type=aes)
  │     EncodingAESKey (43-char Base64) → 32-byte key
  │     Decrypted format: random(16) + msg_len(4) + XML + app_id
  │     Verify trailing app_id matches config; reject otherwise

  ├─ ③ Parse XML, only process:
  │     MsgType = "event"
  │     Event   = "iot_voip_notify"
  │     Action  = "join_voip_room"
  │     Return non-zero errcode for everything else

  ├─ ④ Look up device media profile by Sn (uploaded via /v1/voip/device/profile)
  │     No profile → return errcode=10, cannot deliver call

  ├─ ⑤ Call Tange Cloud POST /v1/token/wxvoip (see section IV)
  │     Input = WeChat callback fields (passthrough) + profile fields + server config
  │     Output = peer_id + token

  ├─ ⑥ Push call_incoming to device via downlink channel (see section V)

  └─ ⑦ Return {"errcode":0, "errmsg":"ok"} to WeChat

3.3 Field Flow Summary

"→ Token" means the field goes into the Tange Cloud Token request. "→ Downlink" means it goes into the call_incoming message.

SourceField→ Token→ DownlinkNotes
WeChat XMLSnDevice ID, used for profile lookup
WeChat XMLRoomIdAs wx_room_id
WeChat XMLSessionKeyAs wx_session_key
WeChat XMLServerTokenAs wx_server_token
WeChat XMLPayloadRaw Base64 passthrough
WeChat URLopenidCaller identity
Device profileAll fieldsA/V parameters
Server configwx_app_id
Server configwx_model_id
Tange Cloud responsepeer_idDevice WHIP connect address
Tange Cloud responsetokenDevice WHIP auth credential

Core principle: nearly all WeChat callback fields are passed through. The server does not interpret them — it only transports and assembles.


IV. Calling the Tange Cloud Token API

POST /v1/token/wxvoipTGV1-HMAC-SHA256 authentication. See the API reference for complete field details and signing.

Called once per join_voip_room callback. Merges WeChat session fields with device capabilities to obtain the WHIP connection credentials the device needs.

Request body:

json
{
  "wx_session_key":      "<WeChat callback passthrough>",
  "wx_room_id":          "<WeChat callback passthrough>",
  "wx_session_token":    "<WeChat callback passthrough>",
  "wx_app_id":           "<Server config>",
  "device_id":           "<WeChat callback Sn>",
  "wx_payload":          "<WeChat callback Payload raw>",
  "wx_model_id":         "<Server config>",
  "calling_timeout_sec": "<Device profile>",
  "no_video":            "<Device profile>",
  "video_mt":            "<Device profile>",
  "screen_width":        "<Device profile>",
  "screen_height":       "<Device profile>",
  "audio_rate":          "<Device profile>",
  "audio_channels":      "<Device profile>"
}

Response:

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

Do not parse peer_id or token. Place them as-is into the downlink call_incoming.


The business server needs a long-connection channel to the device for pushing messages. The transport is up to you (MQTT, WebSocket, custom TCP, etc.). The only requirement: the server can address a specific device when it is online.

The following describes the message contract, independent of transport. For example with MQTT: the server connects to the broker as an MQTT client, sending commands to device/sn_{device_id}/cmd and notifications to device/sn_{device_id}/notify. The same pattern applies to WebSocket or custom TCP — only the routing mechanism differs.

5.1 Message Envelope

All downlink messages use the same JSON structure:

json
{ "type": "<message type>", "channel": "wx", "payload": { ... } }

5.2 Message Categories

CategoryDescriptionRequires device ACK
Command (cmd)Critical operations such as incoming call notificationsYes
Notification (notify)Informational messages such as cancel or authorization changesNo

How to distinguish the two categories depends on the transport. MQTT can use different topics (/cmd vs /notify); WebSocket can add a field like "category": "cmd".

5.3 call_incoming — Incoming Call Notification

Command-type message. Pushed after WeChat callback processing succeeds.

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":          "..."
  }
}

Field sources:

FieldSourceDevice usage
peer_idTange Cloud Token API responseFirst argument to TiRtcWhipConnect
tokenTange Cloud Token API responseSecond argument to TiRtcWhipConnect
wx_app_idServer configIdentifies the Mini Program
wx_model_idServer configIoT model ID
wx_room_idWeChat callback RoomId (passthrough)Needed for hangup/reject
wx_user_openidWeChat callback URL query openid (passthrough)Caller identity
wx_server_tokenWeChat callback ServerToken (passthrough)Pass to TiRtcServiceRequest on reject
wx_session_keyWeChat callback SessionKey (passthrough)Session key
wx_payloadWeChat callback Payload (passthrough)Custom data, Base64 raw
wx_call_idPayload decoded .id (passthrough)Call identifier
wx_fromPayload decoded .from (passthrough)Caller side: "miniapp" or "device"

5.4 call_cancel — Cancel Call

Notification-type message.

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

Triggered when the Mini Program calls /v1/voip/user/cancel. The device matches wx_room_id against the current call and stops ringing or disconnects.

5.5 callers_update — Authorization Change

Notification-type message.

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

Triggered when a user reports or deletes an authorization. The device should re-call GET /v1/voip/device/callers to refresh the local list.


VI. Mini Program–Facing APIs

Called by the Mini Program over HTTPS. Authentication is up to you.

6.1 WeChat Login

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

json
// Request
{ "code": "wx_login_code" }
// Response
{ "code": 0, "data": { "wx_user_openid": "o4DLd5..." } }

Internally calls WeChat jscode2session.

6.2 VoIP Authorization Binding

POST /v1/voip/user/report-auth

Called after the Mini Program completes wx.requestDeviceVoIP.

json
// Request
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5...", "wx_model_id": "HRHY_xxx" }
// Response
{ "code": 0 }

After storing the authorization, push callers_update to the device via the downlink channel.

6.3 Delete Authorization

POST /v1/voip/user/delete-auth

json
// Request
{ "device_id": "TIRZ00000001", "wx_open_id": "o4DLd5..." }
// Response
{ "code": 0 }

6.4 SN Ticket

POST /v1/voip/user/sn-ticket

json
// Request
{ "device_id": "TIRZ00000001" }
// Response
{ "code": 0, "data": { "sn_ticket": "..." } }

Internally calls WeChat getsnticket.

6.5 Cancel Call

POST /v1/voip/user/cancel

json
// Request
{ "device_id": "TIRZ00000001", "wx_room_id": "wxf830863..." }
// Response
{ "code": 0 }

Pushes call_cancel to the device via the downlink channel. Only used when the Mini Program is the caller and the device has not yet answered.


VII. Device-Facing APIs

Called by the device over HTTPS. Authentication is up to you (e.g. JWT).

7.1 Upload Media Capabilities

POST /v1/voip/device/profile

Must be called on startup. Without it, the server cannot retrieve device capabilities when a WeChat callback arrives and the call notification will fail.

json
// Request
{
  "screen_width":        0,
  "screen_height":       0,
  "audio_rate":          8000,
  "audio_channels":      1,
  "video_mt":            "",
  "no_video":            true,
  "calling_timeout_sec": 30
}
// Response
{ "code": 0 }

These fields are assembled into the Tange Cloud Token request. Allowed values follow the Token API:

Profile fieldToken fieldAllowed values (see Server API)
audio_rateaudio_rate8000 / 16000
audio_channelsaudio_channels1 / 2
video_mtvideo_mth264 / mjpeg / none
no_videono_videoSet true for audio-only devices
screen_width/heightscreen_width / screen_heightRequired for non-audio-only
calling_timeout_seccalling_timeout_secRing timeout in seconds, default 30

For finer directional control, add optional fields: up_video_mt (h264/h265/mjpeg/none), down_video_mt (h264/mjpeg/none), down_audio_mt (alaw/amr/opus). Do not mix video_mt with up_video_mt/down_video_mt.

Call once per startup. The server overwrites on each update, then uses the stored values when assembling Token requests.

7.2 List Authorized Callers

GET /v1/voip/device/callers

json
// Response
{ "code": 0, "data": { "list": [
  { "wx_open_id": "o4DLd5...", "wx_app_id": "wx27d...", "wx_model_id": "HRHY...", "created_at": "..." }
] } }

The device selects a target user from this list before placing an active call.

7.3 Device Calls Mini Program

POST /v1/voip/device/call

json
// Request
{ "device_id": "TIRZ00000001", "wx_user_openid": "o4DLd5...", "wx_room_type": "voice" }
// Response
{ "code": 0 }

Internally calls WeChat iot/voip/call. After calling:

  1. WeChat pushes an incoming call ring to the Mini Program
  2. User answers → WeChat callbacks join_voip_room
  3. Subsequent flow is identical to the Mini Program–initiated scenario

VIII. Differences Between the Two Scenarios

The only difference is who initiates the call.

Scenario 1: Mini Program Calls Device

Mini Program VoIP plugin ──▶ WeChat server ── join_voip_room ──▶ Business server

                                                              ① Verify/decrypt
                                                              ② Look up profile
                                                              ③ Call Tange Token API
                                                              ④ Downlink call_incoming ──▶ Device

                                                                                      ⑤ TiRtcWhipConnect
                                                                                      ⑥ Receives cmdw=0x2000 (both sides established)

Server role: purely passive — receives WeChat callback, forwards to device.

Scenario 2: Device Calls Mini Program

Device ── POST /v1/voip/device/call ──▶ Business server ── WeChat iot/voip/call ──▶ WeChat server

                                                                                  Mini Program rings
                                                                                  User answers

                                        Business server ◀── join_voip_room ──── WeChat server

                                      ① Verify/decrypt
                                      ② Look up profile
                                      ③ Call Tange Token API
                                      ④ Downlink call_incoming ──▶ Device

                                                              ⑤ TiRtcWhipConnect
                                                              ⑥ Receives cmdw=0x2000 (both sides established)

Server role: first proactively calls WeChat API to initiate, then passively handles the callback — the callback processing logic is identical to Scenario 1.

Comparison

Scenario 1 (Mini Program calls)Scenario 2 (Device calls)
Call initiationMini Program VoIP pluginDevice calls POST /v1/voip/device/call
Extra server stepNoneCall WeChat iot/voip/call
join_voip_room handlingSameSame
Tange Token APISameSame
Downlink pushSameSame
cmdw=0x2000Received after WhipConnect, both sides establishedSame

WeChat VoIP calling