结果回调接口规范
更新时间:
审核服务向客户提供的 callbackUrl 发起 HTTP POST。通过外层 taskId 关联视频任务;一个直播任务可产生多条片段回调。
POSTcallbackUrl
回调配置与触发
可在控制台配置,或在提交时同时提供非空 callbackUrl 和 callbackSecretKey 覆盖项目配置。只提供其中一个会回退到项目配置,不会部分覆盖。支持 HTTP/HTTPS,建议 HTTPS。
场景
行为
点播
异步完成或失败后回调结果。
直播默认模式
增量推送本次检出的有效片段;普通直播还可能受挂机检出持续时长条件影响。正常片段不一定回调。
直播其他项目模式
可配置累计检出结果,或增量推送所有送检结果。由项目配置决定,不是提交参数;需在接入时确认。
断流
使用 checkType=stream-closed,内容与审核结果不同。主动停止不保证发送断流回调。
请求头与请求体
字段
类型
说明
Content-Type
Header
application/json
signature
Header
下述算法生成的小写 MD5 字符串。
appId
String
视频审核项目编号。
taskId
String
视频任务 ID,不是单次回调的唯一 ID。
checkType
String
video-check / stream-closed
result
String
内层 JSON 序列化后的字符串,不是 JSON 对象。先验签,再解析此字符串。
回调签名
- 解析外层 JSON,取实际收到的非
null 字段值;当前视频回调为四个字符串字段。
- 按字段名 ASCII 升序排列,拼接
key1 + value1 + key2 + value2 + ...,不加分隔符。
- 末尾追加回调密钥,UTF-8 编码后计算 MD5,输出小写十六进制。
- 与请求头
signature 比较。result 使用外层 JSON 解码后得到的原始字符串;不要再次解析、排序、缩进或重新序列化内层 JSON 后验签。
import hashlib
import hmac
import json
def callback_signature(secret, payload):
values = {k: v for k, v in payload.items() if v is not None}
if not all(isinstance(v, str) for v in values.values()):
raise ValueError("Expected string callback fields")
text = "".join(k + values[k] for k in sorted(values)) + secret
return hashlib.md5(text.encode("utf-8")).hexdigest()
def verify_callback(raw_body, received_signature, secret):
payload = json.loads(raw_body)
expected = callback_signature(secret, payload)
if not hmac.compare_digest(expected, received_signature):
raise ValueError("Invalid callback signature")
return payload, json.loads(payload["result"])
固定验签样例
以下仅用于离线验证签名,任务 ID 和密钥不可用于生产:
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"errorCode\":0,\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"code\":0,\"result\":0,\"videoSpams\":[],\"durationMillis\":10000}",
"checkType": "video-check"
}
secretKey = "example-secret"
signature = "2fe38e34dec78f263138004a93511821"
直播片段回调示例
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"errorCode\":0,\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"code\":2,\"result\":2,\"videoSpams\":[{\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"type\":1,\"result\":2,\"beginTime\":1791763205000,\"endTime\":1791763205000,\"checkDuration\":5000,\"duration\":0,\"url\":\"https://media.example.com/evidence/frame.jpg\",\"tags\":[{\"tag\":130,\"level\":2,\"confidence\":0.92,\"wordList\":[\"画面命中词\"]}],\"liveStreamId\":\"publisher-10001\",\"liveUserId\":\"publisher-10001\",\"ocr\":[\"画面命中词\"]},{\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"type\":2,\"result\":2,\"beginTime\":1791763207000,\"endTime\":1791763208000,\"duration\":5000,\"url\":\"https://media.example.com/evidence/audio.wav\",\"text\":\"命中内容\",\"audioText\":\"开头的正常内容,命中内容,结尾的正常内容。\",\"speaker\":\"speaker-1\",\"tags\":[{\"tag\":130,\"level\":2,\"confidence\":0.92,\"wordList\":[\"命中内容\"]}],\"liveStreamId\":\"publisher-10002\",\"liveUserId\":\"publisher-10002\",\"checkDuration\":7000}],\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"extra\":{\"bizId\":\"rtc-10001\"},\"pullStreamSuccess\":true}",
"checkType": "video-check"
}
点播回调内层字段见点播结果查询接口的 videoSpams 说明,直播回调内层字段见直播结果查询接口的 videoSpams 说明。两类片段结构分别定义。同一类型的查询和回调共用字段模型,但片段范围、可选字段及 code 上下文不同;直播片段失败的 code=1 不表示整个直播已结束。
直播新增的 ocr/audioText、tags[].confidence/wordList 和 RTC speaker 按直播证据说明返回。图片与音频可来自不同推流用户,应逐条读取 liveUserId;speaker 不能用作推流 uid。正常片段完整转写仅在项目开启“所有送检结果增量回调”时可返回,不改变默认检出模式。
断流回调
以下字段位于外层 result 字符串解码后的业务对象中;外层结构、签名和成功应答保持不变。
字段
类型
说明
streamUrl
String
普通直播为源 URL;RTC 可能为流 ID 或房间/频道 ID,不一定是 URL。
streamClosed
Boolean
true
checkDuration
Integer
从任务提交到产生断流通知的经过时间,毫秒;不是实际送审音频时长或计费时长。
taskId
String
新增。审核任务 ID,与外层一致。
liveProvider
String
新增,仅 RTC:zego/agora/trtc。
liveRoomId
String
新增,仅 RTC:房间或频道 ID。
result
Integer
可选。生成摘要时已保存结果的累计最高风险等级:0 通过,1 疑似,2 不通过。与外层 result 字符串区分。
spamCount
Integer
可选。摘要快照中已保存的检出证据条数,不是用户数或全部采样数。
pullStreamSuccess
Boolean
可选。已有媒体接收证据时返回 true,与任务最终是否断流无关;省略不代表 false。
zegoErrorReason
Object
可选。仅 zego 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。
agoraErrorReason
Object
可选。仅 agora 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。
trtcErrorReason
Object
可选。仅 trtc 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。
summaryTime
Integer
新增。本次生成摘要的 Unix 毫秒时间戳。
summaryComplete
Boolean
新增,当前固定为 false。断流通知不等待所有在途审核完成,不能据此判断结果已全部到齐。
endReason
String
新增。已确认的终止条件:retry_exhausted/recorder_stopped/unknown,见原因码说明。
lastRecordingReason
String
可选,仅普通 URL 直播。最近一次可用的录制诊断码,可能来自早前重试,不等同于最终停播原因,见诊断码说明。
extra
Object
提交时透传的数据,可能省略。
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"streamUrl\":\"room-10001\",\"streamClosed\":true,\"checkDuration\":60000,\"extra\":{\"bizId\":\"rtc-10001\"},\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"result\":2,\"spamCount\":2,\"pullStreamSuccess\":true,\"summaryTime\":1791763260000,\"summaryComplete\":false,\"endReason\":\"recorder_stopped\"}",
"checkType": "stream-closed"
}
普通 URL 直播摘要示例
以下仅展示外层 result 字符串解码后的业务对象:
{
"streamUrl": "https://media.example.com/live/stream.m3u8",
"streamClosed": true,
"checkDuration": 60000,
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": 2,
"spamCount": 2,
"pullStreamSuccess": true,
"summaryTime": 1791763260000,
"summaryComplete": false,
"endReason": "retry_exhausted",
"lastRecordingReason": "network_timeout"
}
- 摘要反映生成时的已保存结果。尾段和迟到结果仍可继续产生片段回调及查询结果,本次摘要不会随之更新。
- 摘要读取失败、超时或历史摘要不可用时,仍发送断流通知,但省略无法确认的
result/spamCount/pullStreamSuccess;不得把未知结果当作通过。
- 旧版本回调可能不含这些新增字段。主动停止的响应和通知规则保持不变,不会因为新增摘要而承诺额外的结束回调。
成功应答、超时与重试
接收端验签并可靠保存后,应尽快返回 HTTP 200 和以下 JSON;复杂业务处理可在保存后异步执行。
{
"code": 0
}
新接入统一使用 code=0。历史接收端可能使用 HTTP 200 空正文等兼容形式,不作为新接入约定;不要把内部转发服务的 errorCode=0 当成客户应答格式。
单次投递轮次为首次尝试加最多 3 次重试,失败后间隔 10 秒,并受整轮 35 秒超时约束;接收端单次调用超时为 5 秒,因此实际次数可能少于 4 次。未确认的断流通知还可能在后续巡检中重发,不承诺一个事件总共最多 4 次,也不保证只投递一次或严格有序。
按项目、任务、事件类型及片段身份/时间建立业务去重。不要只按 taskId 去重,否则会丢弃同一直播任务的后续片段;累计模式还可能重复携带之前的片段。查询用于补取已保存结果,不保证重现所有正常片段回调。
RTC 异常结束
三方录制进房失败、鉴权失败、录制致命错误或非预期进程退出时,任务按失败结束。正常无人超时、主动停止及正常音频分段不产生平台错误对象。
liveProvider 表示当前平台,trtcErrorReason、agoraErrorReason、zegoErrorReason 仅返回当前平台对应的一项。对象内 errorCode 是具体的平台错误码或录制程序错误码,errorMessage 是可供排查的安全错误说明;它与查询顶层业务码 2107 不同。未取得详细平台错误时返回已确认的 SDK 错误或进程退出原因,不推测原因。
例如 TRTC 进房错误 -3301 的详细原因若为 -100041,将返回 -100041 及 Linux 权限未开通的说明。客户端根据错误对象处理失败,不应把 code=1 或 streamClosed=true 单独视为正常审核完成。
进房失败且没有媒体审核结果时,不返回 result=0;运行中失败时保留已有检出结果,但不代表整条直播审核成功。错误原因会保留供后续查询,重复停止任务不会清除错误。旧任务没有对应错误对象时不能推断为成功或失败。
异常结束沿用 checkType=stream-closed 的回调通道,平台错误对象位于外层 result 字符串解码后的 JSON 中。收到平台错误对象应按失败处理,不能将其当作正常结束。外层封装、签名和确认应答不变;异常通知不保证包含正常断流摘要的可选字段。
失败回调请求体示例:
{
"appId": "1001",
"taskId": "nx_00000000-0000-0000-0000-000000000001_1792639991214",
"checkType": "stream-closed",
"result": "{\"taskId\":\"nx_00000000-0000-0000-0000-000000000001_1792639991214\",\"streamUrl\":\"room-10001\",\"streamClosed\":true,\"checkDuration\":5000,\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"trtcErrorReason\":{\"errorCode\":-100041,\"errorMessage\":\"TRTC 应用未开通 Linux 接入功能,请联系腾讯云开通\"}}"
}
审核服务向客户提供的 callbackUrl 发起 HTTP POST。通过外层 taskId 关联视频任务;一个直播任务可产生多条片段回调。
callbackUrl回调配置与触发
可在控制台配置,或在提交时同时提供非空 callbackUrl 和 callbackSecretKey 覆盖项目配置。只提供其中一个会回退到项目配置,不会部分覆盖。支持 HTTP/HTTPS,建议 HTTPS。
| 场景 | 行为 |
|---|---|
| 点播 | 异步完成或失败后回调结果。 |
| 直播默认模式 | 增量推送本次检出的有效片段;普通直播还可能受挂机检出持续时长条件影响。正常片段不一定回调。 |
| 直播其他项目模式 | 可配置累计检出结果,或增量推送所有送检结果。由项目配置决定,不是提交参数;需在接入时确认。 |
| 断流 | 使用 checkType=stream-closed,内容与审核结果不同。主动停止不保证发送断流回调。 |
请求头与请求体
| 字段 | 类型 | 说明 |
|---|---|---|
Content-Type |
Header | application/json |
signature |
Header | 下述算法生成的小写 MD5 字符串。 |
appId |
String | 视频审核项目编号。 |
taskId |
String | 视频任务 ID,不是单次回调的唯一 ID。 |
checkType |
String | video-check / stream-closed |
result |
String | 内层 JSON 序列化后的字符串,不是 JSON 对象。先验签,再解析此字符串。 |
回调签名
- 解析外层 JSON,取实际收到的非
null字段值;当前视频回调为四个字符串字段。 - 按字段名 ASCII 升序排列,拼接
key1 + value1 + key2 + value2 + ...,不加分隔符。 - 末尾追加回调密钥,UTF-8 编码后计算 MD5,输出小写十六进制。
- 与请求头
signature比较。result使用外层 JSON 解码后得到的原始字符串;不要再次解析、排序、缩进或重新序列化内层 JSON 后验签。
import hashlib
import hmac
import json
def callback_signature(secret, payload):
values = {k: v for k, v in payload.items() if v is not None}
if not all(isinstance(v, str) for v in values.values()):
raise ValueError("Expected string callback fields")
text = "".join(k + values[k] for k in sorted(values)) + secret
return hashlib.md5(text.encode("utf-8")).hexdigest()
def verify_callback(raw_body, received_signature, secret):
payload = json.loads(raw_body)
expected = callback_signature(secret, payload)
if not hmac.compare_digest(expected, received_signature):
raise ValueError("Invalid callback signature")
return payload, json.loads(payload["result"])
固定验签样例
以下仅用于离线验证签名,任务 ID 和密钥不可用于生产:
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"errorCode\":0,\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"code\":0,\"result\":0,\"videoSpams\":[],\"durationMillis\":10000}",
"checkType": "video-check"
}
secretKey = "example-secret"
signature = "2fe38e34dec78f263138004a93511821"
直播片段回调示例
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"errorCode\":0,\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"code\":2,\"result\":2,\"videoSpams\":[{\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"type\":1,\"result\":2,\"beginTime\":1791763205000,\"endTime\":1791763205000,\"checkDuration\":5000,\"duration\":0,\"url\":\"https://media.example.com/evidence/frame.jpg\",\"tags\":[{\"tag\":130,\"level\":2,\"confidence\":0.92,\"wordList\":[\"画面命中词\"]}],\"liveStreamId\":\"publisher-10001\",\"liveUserId\":\"publisher-10001\",\"ocr\":[\"画面命中词\"]},{\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"type\":2,\"result\":2,\"beginTime\":1791763207000,\"endTime\":1791763208000,\"duration\":5000,\"url\":\"https://media.example.com/evidence/audio.wav\",\"text\":\"命中内容\",\"audioText\":\"开头的正常内容,命中内容,结尾的正常内容。\",\"speaker\":\"speaker-1\",\"tags\":[{\"tag\":130,\"level\":2,\"confidence\":0.92,\"wordList\":[\"命中内容\"]}],\"liveStreamId\":\"publisher-10002\",\"liveUserId\":\"publisher-10002\",\"checkDuration\":7000}],\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"extra\":{\"bizId\":\"rtc-10001\"},\"pullStreamSuccess\":true}",
"checkType": "video-check"
}
点播回调内层字段见点播结果查询接口的 videoSpams 说明,直播回调内层字段见直播结果查询接口的 videoSpams 说明。两类片段结构分别定义。同一类型的查询和回调共用字段模型,但片段范围、可选字段及 code 上下文不同;直播片段失败的 code=1 不表示整个直播已结束。
直播新增的 ocr/audioText、tags[].confidence/wordList 和 RTC speaker 按直播证据说明返回。图片与音频可来自不同推流用户,应逐条读取 liveUserId;speaker 不能用作推流 uid。正常片段完整转写仅在项目开启“所有送检结果增量回调”时可返回,不改变默认检出模式。
断流回调
以下字段位于外层 result 字符串解码后的业务对象中;外层结构、签名和成功应答保持不变。
| 字段 | 类型 | 说明 |
|---|---|---|
streamUrl |
String | 普通直播为源 URL;RTC 可能为流 ID 或房间/频道 ID,不一定是 URL。 |
streamClosed |
Boolean | true |
checkDuration |
Integer | 从任务提交到产生断流通知的经过时间,毫秒;不是实际送审音频时长或计费时长。 |
taskId |
String | 新增。审核任务 ID,与外层一致。 |
liveProvider |
String | 新增,仅 RTC:zego/agora/trtc。 |
liveRoomId |
String | 新增,仅 RTC:房间或频道 ID。 |
result |
Integer | 可选。生成摘要时已保存结果的累计最高风险等级:0 通过,1 疑似,2 不通过。与外层 result 字符串区分。 |
spamCount |
Integer | 可选。摘要快照中已保存的检出证据条数,不是用户数或全部采样数。 |
pullStreamSuccess |
Boolean | 可选。已有媒体接收证据时返回 true,与任务最终是否断流无关;省略不代表 false。 |
zegoErrorReason |
Object | 可选。仅 zego 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。 |
agoraErrorReason |
Object | 可选。仅 agora 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。 |
trtcErrorReason |
Object | 可选。仅 trtc 任务异常终止时返回;包含 errorCode 和 errorMessage,正常结束时省略。 |
summaryTime |
Integer | 新增。本次生成摘要的 Unix 毫秒时间戳。 |
summaryComplete |
Boolean | 新增,当前固定为 false。断流通知不等待所有在途审核完成,不能据此判断结果已全部到齐。 |
endReason |
String | 新增。已确认的终止条件:retry_exhausted/recorder_stopped/unknown,见原因码说明。 |
lastRecordingReason |
String | 可选,仅普通 URL 直播。最近一次可用的录制诊断码,可能来自早前重试,不等同于最终停播原因,见诊断码说明。 |
extra |
Object | 提交时透传的数据,可能省略。 |
{
"appId": "1001",
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": "{\"streamUrl\":\"room-10001\",\"streamClosed\":true,\"checkDuration\":60000,\"extra\":{\"bizId\":\"rtc-10001\"},\"taskId\":\"cn_00000000-0000-0000-0000-000000000001_1791763200000\",\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"result\":2,\"spamCount\":2,\"pullStreamSuccess\":true,\"summaryTime\":1791763260000,\"summaryComplete\":false,\"endReason\":\"recorder_stopped\"}",
"checkType": "stream-closed"
}
普通 URL 直播摘要示例
以下仅展示外层 result 字符串解码后的业务对象:
{
"streamUrl": "https://media.example.com/live/stream.m3u8",
"streamClosed": true,
"checkDuration": 60000,
"taskId": "cn_00000000-0000-0000-0000-000000000001_1791763200000",
"result": 2,
"spamCount": 2,
"pullStreamSuccess": true,
"summaryTime": 1791763260000,
"summaryComplete": false,
"endReason": "retry_exhausted",
"lastRecordingReason": "network_timeout"
}
- 摘要反映生成时的已保存结果。尾段和迟到结果仍可继续产生片段回调及查询结果,本次摘要不会随之更新。
- 摘要读取失败、超时或历史摘要不可用时,仍发送断流通知,但省略无法确认的
result/spamCount/pullStreamSuccess;不得把未知结果当作通过。 - 旧版本回调可能不含这些新增字段。主动停止的响应和通知规则保持不变,不会因为新增摘要而承诺额外的结束回调。
成功应答、超时与重试
接收端验签并可靠保存后,应尽快返回 HTTP 200 和以下 JSON;复杂业务处理可在保存后异步执行。
{
"code": 0
}
新接入统一使用 code=0。历史接收端可能使用 HTTP 200 空正文等兼容形式,不作为新接入约定;不要把内部转发服务的 errorCode=0 当成客户应答格式。
单次投递轮次为首次尝试加最多 3 次重试,失败后间隔 10 秒,并受整轮 35 秒超时约束;接收端单次调用超时为 5 秒,因此实际次数可能少于 4 次。未确认的断流通知还可能在后续巡检中重发,不承诺一个事件总共最多 4 次,也不保证只投递一次或严格有序。
按项目、任务、事件类型及片段身份/时间建立业务去重。不要只按 taskId 去重,否则会丢弃同一直播任务的后续片段;累计模式还可能重复携带之前的片段。查询用于补取已保存结果,不保证重现所有正常片段回调。
RTC 异常结束
三方录制进房失败、鉴权失败、录制致命错误或非预期进程退出时,任务按失败结束。正常无人超时、主动停止及正常音频分段不产生平台错误对象。
liveProvider 表示当前平台,trtcErrorReason、agoraErrorReason、zegoErrorReason 仅返回当前平台对应的一项。对象内 errorCode 是具体的平台错误码或录制程序错误码,errorMessage 是可供排查的安全错误说明;它与查询顶层业务码 2107 不同。未取得详细平台错误时返回已确认的 SDK 错误或进程退出原因,不推测原因。
例如 TRTC 进房错误 -3301 的详细原因若为 -100041,将返回 -100041 及 Linux 权限未开通的说明。客户端根据错误对象处理失败,不应把 code=1 或 streamClosed=true 单独视为正常审核完成。
进房失败且没有媒体审核结果时,不返回 result=0;运行中失败时保留已有检出结果,但不代表整条直播审核成功。错误原因会保留供后续查询,重复停止任务不会清除错误。旧任务没有对应错误对象时不能推断为成功或失败。
异常结束沿用 checkType=stream-closed 的回调通道,平台错误对象位于外层 result 字符串解码后的 JSON 中。收到平台错误对象应按失败处理,不能将其当作正常结束。外层封装、签名和确认应答不变;异常通知不保证包含正常断流摘要的可选字段。
失败回调请求体示例:
{
"appId": "1001",
"taskId": "nx_00000000-0000-0000-0000-000000000001_1792639991214",
"checkType": "stream-closed",
"result": "{\"taskId\":\"nx_00000000-0000-0000-0000-000000000001_1792639991214\",\"streamUrl\":\"room-10001\",\"streamClosed\":true,\"checkDuration\":5000,\"liveProvider\":\"trtc\",\"liveRoomId\":\"room-10001\",\"trtcErrorReason\":{\"errorCode\":-100041,\"errorMessage\":\"TRTC 应用未开通 Linux 接入功能,请联系腾讯云开通\"}}"
}