Device Integration
WeChat VoIP calling involves four parties: Mini Program, business server, Tange Cloud platform, and device. The device is based on the Tange Cloud Nano SDK and is responsible for processing downlink call notifications, managing call state, establishing WHIP connections, and exchanging signaling and media streams.
Interface ownership:
- Tange Cloud provides: Nano SDK C API (
TiRtcWhipConnect,TiRtcSendCommand,TiRtcServiceRequest, etc.) and signaling interaction (cmdw=0x2000/0x2001)- Business server provides: HTTP APIs (
/v1/voip/device/*) and downlink channel messages (call_incoming, etc.) — implement per Business Server Integration- WeChat provides: Mini Program VoIP plugin,
iot_voip_notifycallback
Read Call flows and Business Server Integration first.
I. What to Implement
In development order:
| # | Module | Purpose |
|---|---|---|
| 1 | SDK initialization | TiRtcInit + TiRtcStart, wait for SYS_STARTED |
| 2 | Upload media capabilities | Call POST /v1/voip/device/profile on startup |
| 3 | Listen on downlink channel | Receive call_incoming, call_cancel, callers_update |
| 4 | Incoming call handling | Decide answer/reject/busy, establish WHIP connection |
| 5 | Outgoing call handling | Call POST /v1/voip/device/call → wait for callback → WHIP connect |
| 6 | In-call | Send/receive audio, handle hangup signaling (0x2001) |
| 7 | Session cleanup | Disconnect, release resources, return to idle |
II. SDK Initialization
Start the Nano SDK before any call operations.
#include "tiRTC.h"
static TIRTCCALLBACKS cbs; // Must be static or global — SDK stores pointer only
// License format: "device_id,device_key"
char license[256];
snprintf(license, sizeof(license), "%s,%s", device_id, device_key);
// Optional: override service endpoint
TiRtcSetOption(TIRTC_OPT_SERVICE_ENDPOINT, endpoint, strlen(endpoint));
memset(&cbs, 0, sizeof(cbs));
cbs.on_event = on_event;
cbs.on_command = on_command; // Receive peer signaling
cbs.on_audio = on_audio; // Receive downlink audio
cbs.on_conn_error = on_conn_error;
cbs.on_disconnected = on_disconnected;
// Fill remaining callbacks with no-op functions — must not be NULL
TiRtcStart(license, &cbs);
// Wait for on_event(TIRTC_EVENT_SYS_STARTED) before any connection operationsRequired callbacks:
| Callback | Required | Purpose |
|---|---|---|
on_event | Yes | Wait for SYS_STARTED / SYS_STOPPED |
on_command | Yes | Receive peer hangup 0x2001, execute disconnect |
on_audio | Yes | Receive downlink audio data |
on_conn_error | Yes | Clean up session state on connection error |
on_disconnected | Yes | Clean up session state on peer disconnect |
| Others | No-op required | SDK requires all fields non-NULL |
III. Calling Business Server APIs
The following HTTP APIs are implemented by the business server per Business Server Integration. The device calls them over HTTPS.
3.1 Upload Media Capabilities
POST /v1/voip/device/profile
Must be called on startup. Without it, call notifications will fail.
// Request (authentication per your design, e.g. JWT)
{
"screen_width": 0,
"screen_height": 0,
"audio_rate": 8000,
"audio_channels": 1,
"video_mt": "",
"no_video": true,
"calling_timeout_sec": 30
}Allowed values follow the Tange Cloud Token API. Call once per startup.
3.2 List Authorized Callers
GET /v1/voip/device/callers
Returns the list of authorized WeChat users for active call target selection.
3.3 Device Calls Mini Program
POST /v1/voip/device/call
{ "device_id": "TIRZ00000001", "wx_user_openid": "o4DLd5...", "wx_room_type": "voice" }On success, enter the calling window. The server internally calls WeChat iot/voip/call. Once the user answers, the device receives call_incoming.
IV. Listening for Downlink Messages (Business Server Push)
The business server pushes messages over a long-connection channel. Transport is up to you. For example with MQTT: the device connects as sn_{device_id}. See Business Server Integration · Device Downlink Channel for the full message format.
All messages use the JSON envelope {"type": "...", "channel": "wx", "payload": {...}}.
4.1 call_incoming — Incoming Call
{
"type": "call_incoming",
"channel": "wx",
"payload": {
"peer_id": "whips://wxvoip?...",
"token": "v1.eyJ...",
"wx_room_id": "wxf830863...",
"wx_user_openid": "o4DLd5...",
"wx_server_token": "...",
"wx_session_key": "...",
"wx_payload": "...",
"wx_from": "miniapp"
}
}The device must reply with an ACK before entering call state logic.
4.2 call_cancel — Cancel Call
{ "type": "call_cancel", "channel": "wx", "payload": { "wx_room_id": "wxf830863..." } }Match wx_room_id against the current call; if matched, stop ringing or disconnect.
4.3 callers_update — Authorization Change
{ "type": "callers_update", "channel": "wx", "payload": {} }On receipt, re-call GET /v1/voip/device/callers to refresh the local list.
V. Incoming Call Handling (Device as Callee)
When the device receives call_incoming, determine behavior based on current state:
Receive call_incoming
│
├─ Already in call (IN_CALL) → Reject, reason=7 (user busy)
│ TiRtcServiceRequest("/v1/wxvoip/reject", ...)
│
├─ Already have a pending incoming call → Reject, reason=7
│
├─ Outgoing call in progress, openid matches → Auto-answer (callback scenario)
│ TiRtcWhipConnect → Receives 0x2000 (both sides established)
│
├─ Outgoing call in progress, openid mismatch → Reject, reason=5 (device busy)
│
└─ Idle → Ring, wait for user action
├─ Answer → TiRtcWhipConnect → Receives 0x2000 (both sides established)
└─ Reject → TiRtcServiceRequest("/v1/wxvoip/reject", reason=7)5.1 Establishing WHIP Connection (Answer)
#include "tiRTC.h"
static void on_whip_connect(int err, tirtc_conn_t hconn, void *user_data) {
if (err != 0) {
// err is TIRTC_E_*; use TiRtcGetErrorStr(err) for description
return;
}
// WHIP connected; on_command will receive cmdw=0x2000 (both sides established)
}
// Callee answering
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);
// Caller callback
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);
// Return 0 only means parameter validation passed; real result in callbackImportant:
peer_idbuffer must be ≥ 1024 bytes (contains URL-encoded session_key / session_token of variable length).
5.2 Reject (Before Connection)
Before calling TiRtcWhipConnect, use the service request API instead of the command channel:
#include "tiRTC.h"
// All fields from call_incoming payload
char body[1024];
snprintf(body, sizeof(body),
"{"
"\"wx_app_id\":\"%s\","
"\"wx_model_id\":\"%s\","
"\"wx_session_token\":\"%s\","
"\"wx_room_id\":\"%s\","
"\"wx_payload\":\"%s\","
"\"hangup_reason\":%d"
"}",
wx_app_id, wx_model_id, wx_server_token, wx_room_id, wx_payload, reason);
TiRtcServiceRequest("/v1/wxvoip/reject", body, NULL, on_reject_resp, NULL);| Field | Source |
|---|---|
wx_app_id | call_incoming payload |
wx_model_id | call_incoming payload |
wx_session_token | call_incoming payload wx_server_token |
wx_room_id | call_incoming payload |
wx_payload | call_incoming payload, may be empty |
hangup_reason | 5=busy (outgoing conflict), 7=user reject |
See Device Service Request API.
VI. Outgoing Call Handling (Device as Caller)
6.1 Initiate Call
Call the business server POST /v1/voip/device/call (see 3.3). On success, enter the calling window.
6.2 Receiving the Callback
When the device receives call_incoming with wx_user_openid matching the outgoing target → auto-answer:
// Establish WHIP connection; on_command receives 0x2000 (both sides established)
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);6.3 Abort Call
Cancel within the calling window. If no WHIP connection yet, mark local state as ended. If already connected:
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);VII. In-Call Signaling and Media
7.1 Signaling
| Command | Direction | Meaning | When |
|---|---|---|---|
0x2000 | Platform → Device | Connected | Received after WHIP connect, both sides established |
0x2001 | Bidirectional | Hangup | Device sends (active hangup) / Device receives (peer hangup) |
Send hangup (device initiated):
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);Receive hangup (peer or cloud initiated):
static void on_command(tirtc_conn_t hconn, uint32_t cmdw,
const void *data, uint32_t len) {
if (cmdw == 0x2001) {
TiRtcDisconnect(hconn); // Release resources
}
}See WeChat VoIP Calling Commands.
7.2 Audio/Video Send/Receive
| Parameter | Value |
|---|---|
stream_id (audio) | 10 |
stream_id (video) | 11 |
Audio: Encoding depends on the device's reported down_audio_mt — alaw, amr, or opus (default alaw). Example with G.711 A-law (8kHz / mono / 320 bytes per 40ms frame):
TIRTCFRAMEINFO fi;
memset(&fi, 0, sizeof(fi));
fi.stream_id = 10;
fi.media = TIRTC_AUDIO_ALAW; // G.711 A-law; use corresponding enum for amr/opus
fi.flags = TIRTC_AUDIOSAMPLE_8K16B1C; // 8kHz, 16bit, 1ch
fi.ts = (uint32_t)(pts_ms & 0xFFFFFFFF);
fi.length = 320; // G.711a 8kHz 1ch = 320 bytes/40ms
int rc = TiRtcSendAudioStream(hconn, &fi, pkt);
// rc > 0: bytes sent
// rc < 0: error; TIRTC_E_INVALID_HANDLE(-40002) briefly retryFrame parameters vary by codec — see the Tange Cloud Token API field reference. audio_rate supports 8000 / 16000; audio_channels supports 1 / 2.
Video: Encoding depends on video_mt or up_video_mt/down_video_mt — h264, h265, mjpeg, or none. Audio-only devices set no_video: true.
Receive: on_audio / on_video callbacks deliver peer audio/video frames.
VIII. Implementation Notes
| Point | Detail |
|---|---|
peer_id — do not assemble yourself | Comes from server call_incoming; pass as-is to TiRtcWhipConnect |
token — do not issue yourself | Generated by Tange Cloud Token API |
| Buffer size | peer_id string contains URL-encoded params; ≥ 1024 bytes recommended |
| Callbacks must not block | SDK callbacks run on internal threads; only set flags, no sleep/IO |
TiRtcWhipConnect return 0 ≠ success | Only means params passed validation; real result in callback err |
TIRTCCALLBACKS must be static | SDK stores pointer only; local variable invalid after function returns |
0x2000 sent by platform | Received via on_command after WHIP connect; device never sends it |
| Reject before WHIP connect | Use TiRtcServiceRequest, not 0x2001 |
Complete SDK API: TiRTC · C API Reference. Service request API: Device Service Request API. Signaling: WeChat VoIP Calling Commands.