Skip to content

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_notify callback

Read Call flows and Business Server Integration first.


I. What to Implement

In development order:

#ModulePurpose
1SDK initializationTiRtcInit + TiRtcStart, wait for SYS_STARTED
2Upload media capabilitiesCall POST /v1/voip/device/profile on startup
3Listen on downlink channelReceive call_incoming, call_cancel, callers_update
4Incoming call handlingDecide answer/reject/busy, establish WHIP connection
5Outgoing call handlingCall POST /v1/voip/device/call → wait for callback → WHIP connect
6In-callSend/receive audio, handle hangup signaling (0x2001)
7Session cleanupDisconnect, release resources, return to idle

II. SDK Initialization

Start the Nano SDK before any call operations.

c
#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 operations

Required callbacks:

CallbackRequiredPurpose
on_eventYesWait for SYS_STARTED / SYS_STOPPED
on_commandYesReceive peer hangup 0x2001, execute disconnect
on_audioYesReceive downlink audio data
on_conn_errorYesClean up session state on connection error
on_disconnectedYesClean up session state on peer disconnect
OthersNo-op requiredSDK 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.

json
// 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

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


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

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

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

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

c
#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 callback

Important: peer_id buffer 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:

c
#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);
FieldSource
wx_app_idcall_incoming payload
wx_model_idcall_incoming payload
wx_session_tokencall_incoming payload wx_server_token
wx_room_idcall_incoming payload
wx_payloadcall_incoming payload, may be empty
hangup_reason5=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:

c
// 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:

c
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);

VII. In-Call Signaling and Media

7.1 Signaling

CommandDirectionMeaningWhen
0x2000Platform → DeviceConnectedReceived after WHIP connect, both sides established
0x2001BidirectionalHangupDevice sends (active hangup) / Device receives (peer hangup)

Send hangup (device initiated):

c
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);

Receive hangup (peer or cloud initiated):

c
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

ParameterValue
stream_id (audio)10
stream_id (video)11

Audio: Encoding depends on the device's reported down_audio_mtalaw, amr, or opus (default alaw). Example with G.711 A-law (8kHz / mono / 320 bytes per 40ms frame):

c
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 retry

Frame 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_mth264, 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

PointDetail
peer_id — do not assemble yourselfComes from server call_incoming; pass as-is to TiRtcWhipConnect
token — do not issue yourselfGenerated by Tange Cloud Token API
Buffer sizepeer_id string contains URL-encoded params; ≥ 1024 bytes recommended
Callbacks must not blockSDK callbacks run on internal threads; only set flags, no sleep/IO
TiRtcWhipConnect return 0 ≠ successOnly means params passed validation; real result in callback err
TIRTCCALLBACKS must be staticSDK stores pointer only; local variable invalid after function returns
0x2000 sent by platformReceived via on_command after WHIP connect; device never sends it
Reject before WHIP connectUse TiRtcServiceRequest, not 0x2001

Complete SDK API: TiRTC · C API Reference. Service request API: Device Service Request API. Signaling: WeChat VoIP Calling Commands.

WeChat VoIP calling