Request format
We send a POST request to thecb_url you registered. The event name appears in both the query parameters and the request body:
data; the three outer fields are always the same. time is the Unix timestamp (in seconds) when the event occurred.
Verify the signature (required)
The signature in the callback request headers uses exactly the same algorithm as calls to the server API (see the Overview), just in the opposite direction: this time we sign with yourapp_key and you verify. The request headers are app_id / nonce / timestamp / signature.
Without signature verification, anyone who knows your callback URL can forge events. Don’t skip this step.
Response requirements
- The HTTP status code must be
200 - The response body must be standard JSON:
{"code": 0} - Our request timeout is 10 seconds. Your handler must return within that time (move heavy work to async processing)
code counts as a failure.
Events at a glance
user_enter — User enters the meeting
role: 0 regular member, 1 host, 2 co-host.
When the same user_id enters the meeting on multiple devices, the event fires once for each device.
user_exit — User exits the meeting
Has two more fields than user_enter:
reason:1exited voluntarily,2removed,3replaced by the same user entering again,4heartbeat timeout,5meeting destroyed,6switched to audienceonlineis the number of online members in the meeting after this user exits;0means everyone has left
online == 0 to tell that “the meeting is actually empty” is more reliable than keeping your own count.
meeting_status_change — Meeting status changes
1 not started, 2 in progress, 3 ended. from_status is included so you can tell
a “normal start” (1→2) apart from cases like “an abnormal restart”.
mcu_status_change — Recording task status changes
task_typeis a bit mask; for the values, see the Cloud recording and live streaming guidetask_status:0pending,1in progress,2ending,3ended abnormally,4ended normallyerr_deschas content only whentask_status=3record_count/total_duration/total_sizeare the number of files, total duration (seconds), and total bytes produced by this recording
0 and 2 are transitional states. Your backend usually only needs to care about 1 (started), 3 (failed), and 4 (ended).
When the task ends, record_count in this event is often still 0: recording files start transcoding and uploading only after the task ends.
After all files are uploaded, this event is sent again, and the counts in that one are complete.
mcu_record_done — A recording file is ready
task_status changes to 4 (ended normally), the file
is often still being transcoded and uploaded, so requesting the URL then may return nothing.
One recording of a meeting produces multiple files, and this event is sent per file. A recording longer than the segment limit (1 hour by default)
is split into segments by duration, and a recording that resumes after an interruption also starts a new segment. So:
- Get the playback URL by
record_id, nottask_id. See Get the playback URL of a recording file seqstarts at 1, and sorting by it gives the playback order;offset_msis the offset (milliseconds) from the start of the whole recording, for continuous playbackreason:1split by duration,2resumed after an interruption (there is a time gap from the previous segment),3final segment at the end,0unknownis_lastistruewhen all files of this recording are ready
duration is this segment’s duration (seconds), and vod_size is its size in bytes.
mcu_alarm — Recording task error
alarm_brief above means “Stream push interrupted”.) gw is the gateway node running the task. Giving us this value speeds up troubleshooting a lot. Note that an alarm doesn’t mean the task has stopped—
rely on task_status in mcu_status_change.
Idempotency and retry
Network jitter can cause the same event to be delivered more than once. Make your handling idempotent byevent + business key (meeting_id, task_id,
user_id). Don’t deduplicate by time—there can be several events of the same type within the same second.