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 server API calls (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 5 seconds. Your handler must return within that time (move heavy work to async processing)
Only
agent_join and agent_operate are synchronous; all others are asynchronous notifications.
Asynchronous notification events
channel_open — Channel opened
channel_destroy — Channel destroyed
reason: 1 destroyed explicitly (the destroy endpoint was called), 2 destroyed automatically because the online count reached 0.
user_join — User joins the channel
user_leave — User leaves the channel
reason:1left voluntarily,2removed,3replaced by a later join with the sameuid,4heartbeat timeout,5channel destroyed,6switched to audienceonlineis the number of online users in the channel after this user left;0means the channel is now empty
im_connect / im_disconnect — IM device online / offline
im_disconnect has an extra reason, with the same values as user_leave.
device_type: 0 unknown, 1 Windows, 2 Android, 3 iOS, 4 Linux, 5 macOS, 6 WebRTC, 7 WeChat Mini Program.
From 80 up is the range for agents joining on the server side (81 MCU, 82 SIP, 83 H.323, 84 GB28181, 85 RTSP, 86 RTMP, 87 file playback, 88 stream distribution, 89 transcription).
agent_online / agent_offline — Device online / offline
Tells you whether a device is online outside the channel: a phone or GB28181 camera is online once it registers with the device gateway, and offline once it unregisters or disconnects.
This is unrelated to whether it’s in a channel (for joining and leaving a channel, see user_join / user_leave).
agent_offline has two extra fields:
idis the device ID in List devices;gwis the gateway the device is ontype:2SIP,3H.323,4GB28181 surveillancecontactidentifies the device: the registration username for SIP, the short number for H.323, and the device number for GB28181 (sip_no, not a channel number)reason:1the device unregistered,4heartbeat timeoutheartbeat_atis the time of the last heartbeat. An offline event caused by heartbeat timeout waits for the timeout to be determined, so it’s sent 5–7 minutes after the actual disconnect. When you need the actual disconnect time, use this field
regsip, regh323, gb28181) have an online status and send these two events;
with direct IP and RTSP stream pull, we connect to the device ourselves, so there is no online or offline.
Each status change is sent only once: periodic registration refreshes and GB28181 Keepalives don’t send agent_online again;
when heartbeats resume after going offline, an online event is sent again. Devices don’t belong to a particular app, so every app that subscribes to this event receives it.
mcu_task — Recording, stream mixing, or live streaming task status changes
task_typeis a bit mask; for its meaning, see the Cloud recording and live streaming guidetask_status:0pending,1in progress,2ending,3ended abnormally,4ended normallyerr_deschas content only when the task ended abnormallyrecord_count/total_duration/total_sizeare the number of files, total duration (seconds), and total bytes produced by this recording
record_count in this event is often still 0: recording files start transcoding and uploading only after the task ends.
Once all files are uploaded, we send this event once more, and the counts in that one are complete. Use it for reconciliation.
mcu_record — A recording file is complete
- The recording is longer than the segment limit (1 hour by default): it is split into rolling segments by duration during transcoding, with continuous time between segments
- The underlying recording stops automatically partway through because there has been no audio or video for 30 seconds, and is then restarted: a new segment starts, with a time gap between segments
- Get playback URLs by
record_id, nottask_id—task_idbelongs to the task, and one task has severalrecord_ids. Pass it when calling Get a recording file’s playback URL seqstarts at 1; sorting by it gives the playback orderoffset_msis this segment’s offset (in milliseconds) from the start of the whole recording; use it to build the progress bar for continuous playback of several segments.began_at/ended_atare wall-clock times, for aligning with the timeline of your own businessreason:1split by duration,2resumed after an interruption (there is a gap from the previous segment; continuous playback jumps there, so it’s worth showing a hint in your UI),3final segment when the task ended,0unknownis_lastbeingtruemeans all files of this recording have been produced, and you can start assembling the complete playback
vod_size is in bytes; duration is in seconds.
mcu_alarm — Recording task alarm
alarm_brief above means “Task ended abnormally”.) Use it to feed your own alerting system. Recording is billed continuously, and if no one notices an abnormal interruption, what you lose is the recording itself.
talkrec_task — Voice recording task status changes
task_status: 0 pending, 1 in progress, 3 ended abnormally, 4 ended normally (there is no “ending” state as in mcu).
Starting voice recording is asynchronous—calling the endpoint only means the request was accepted. The task changes to in progress only after the voice recording gateway joins the channel successfully,
so this event is the most reliable signal of “whether voice recording is actually running”.
talkrec_record — A voice segment is complete
record_idis the segment ID used to get the playback URL, nottask_idduration_msis in milliseconds (segments are usually only a few seconds long, so second-level precision isn’t enough)end_reason, why the segment closed:0unknown,1normal end (talk button released),2split on timeout,3user left,4task stopped,5idle timeout (fallback)
end_reason is worth watching: 2 means the segment was cut off by the duration limit (one utterance is split into several segments),
and 5 means the server closed the segment as a fallback. If it appears often, silence detection isn’t wrapping up segments properly, and it’s worth checking the audio quality.
Synchronous events
For these two events, we wait for your answer before continuing, so your handling must be fast (5-second timeout).agent_join — Device requests to join the channel
type is the agent type: 1 MCU, 2 SIP, 3 H.323, 4 GB28181 surveillance, 5 RTSP stream pull, 6 RTMP stream pull, 7 file playback, 8 stream distribution, 9 transcription.
no is the target room number the device wants to join (the meeting number in your own business).
You need to turn it into a session: use no to find the corresponding channel, call Get a channel join token to get a sid, and return it to us as is:
code rejects the device’s join. If your user details don’t need extended props and the device is unconditionally trusted, you can skip subscribing to this event.
agent_operate — A device in the channel is controlled (microphone or camera on/off)
code rejects the operation. If you don’t subscribe to this event, all media operations on devices are allowed automatically.
Idempotency and ordering
Callbacks are delivered at least once, with no guarantee of ordering or uniqueness:- Retries lead to duplicate deliveries, so make your handling idempotent by a combination such as
channel+uid+time - The network and queues can reorder events, so don’t rely on “join arrives before leave” to track state yourself; when you need authoritative state, use the query endpoints