结果回调接口规范

审核服务向客户提供的 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 对象。先验签,再解析此字符串。

回调签名

  1. 解析外层 JSON,取实际收到的非 null 字段值;当前视频回调为四个字符串字段。
  2. 按字段名 ASCII 升序排列,拼接 key1 + value1 + key2 + value2 + ...,不加分隔符。
  3. 末尾追加回调密钥,UTF-8 编码后计算 MD5,输出小写十六进制。
  4. 与请求头 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 接入功能,请联系腾讯云开通\"}}"
}