Webhook是一种基于HTTP/HTTPS协议的回调机制,允许服务端主动推送数据。RTC回调通知服务器使用Webhook将相关事件回调给开发者服务器,以便开发者按需处理自己的业务逻辑。

image

使用方法

前提条件

使用流程

  1. 在控制台开通某个AppID的事件回调功能。

    登录RTC控制台,在左侧导航栏选择配置管理 > 事件通知 > 选中对应的AppID,进行回调设置页面。按需配置具体的事件。

image

  1. 触发回调事件。

    完成AppID应用事件通知配置之后,您可以通过服务端API,发起相关任务,比如开启录制、开启推流等操作来触发相应的回调事件产生。

  2. 接收回调事件。

    当回调事件产生之后,比如录制文件生成,若回调成功,您可以在您部署的回调接受服务器中查看具体的回调事件通知。

回调机制

  1. 您需要自行部署一个HTTP服务来接收回调消息,并在控制台中具体业务中配置回调URL。

  2. 当事件产生时,RTC回调通知服务器会向该URL发起HTTP POST请求,事件通知内容将通过HTTP Body送达。

  3. 您的HTTP服务对HTTP POST请求进行响应且HTTP状态码为200,即视为回调成功;若响应其他状态码或响应超时,则视为回调失败。

  4. 回调成功后,您配置的回调URL中将接收到相应的事件通知内容。

回调格式

回调消息以HTTP POST请求发送到您的服务器,请求Body格式为JSON。字符编码为UTF-8。

回调消息的请求Header 中包含以下字段:

字段

示例值

描述

Content-Type

application/json

固定值

trace-id

2401058********622012463d9

该字段用于排查问题使用

DingRTC-Signature

z5jbvxxx.1718877424.xx3e7691142ffe4342e13e25dc317695b17827e34ec248a5cc35d3a7e1e1cd44

RTC回调服务加密算法生成的加密值。详见验证签名

回调消息请求的Body中包含以下字段:

名称

类型

是否必须

示例值

描述

eventId

string

12343aed*********

事件ID

eventType

string

101

事件类型,RTC回调服务器有个eventType,具体类型见下文回调消息列表

notifyTime

long

1701056041128

通知时间戳,单位:毫秒

eventData

JSONObject

{"appId": "z7***u8v"}

回调消息具体内容,每种类型的事件不一样,具体见下文回调消息列表

重要
  • 您的服务器收到的通知顺序和事件发生的顺序不一定完全一致。

  • 为确保回调消息通知的可靠性,每次事件可能会有不止一次消息通知,您的服务器可能需要做消息幂等处理。

验证签名

RTC回调服务器在通知客户服务器时,为了校验本次请求的合法性,约定签名算法如下。

消息头中的 DingRTC-Signature 由三部分组成,用.拼接在一起。格式为AppId.TimeStamp.Signature,字段含义如下:

  • AppID: 应用ID。

  • TimeStamp:UTC时间戳(精确到秒)。

  • Signature:签名,由HTTP请求内容的原始字符串、时间戳、回调密钥计算得出,详细算法如下:

Signature=hexString(HmacSHA256(请求内容的原始字符串+TimeStamp,callbackSecret))

回调通知密钥请通过控制台获取。

验证签名时,您可以参考以下代码:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class SignUtil {

    public static final String HMAC_SHA_256 = "HmacSHA256";

    public static String hmacSha256(String message, String secret) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA_256);
            Mac mac = Mac.getInstance(HMAC_SHA_256);
            mac.init(signingKey);
            byte[] rawHmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(rawHmac);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String bytesToHex(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for (byte b : bytes) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() < 2) {
                sb.append(0);
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        String requestBody = "{\"eventData\":{\"channelId\":\"55\",\"timestamp\":1718877424674},\"eventId\":\"2133cc0c17188774246986428d0cb0\",\"eventType\":\"101\",\"notifyTime\":1718877424701}";
        String secret = "your callback secret";
        String signatureHeader = "z5jbvxxx.1718877424.150f2b8e107a0f4399671dcf2b1e3e2ac78252a26c9626abf4a29a77464a96c1";
        String appId = signatureHeader.split("\\.")[0];
        String timestamp = signatureHeader.split("\\.")[1];
        String signature = signatureHeader.split("\\.")[2];
        if (signature.equals(hmacSha256(requestBody + timestamp, secret))) {
            System.out.println("DingRTC-Signature is valid");
        } else {
            System.out.println("DingRTC-Signature is invalid");
        }
    }
}

# !-*- coding: utf-8 -*-
import hashlib
import hmac

request_body='{"eventData":{"channelId":"55","timestamp":1718877424674},"eventId":"2133cc0c17188774246986428d0cb0","eventType":"101","notifyTime":1718877424701}'
secret = 'your callback secret'
signature_header = 'z5jbvxxx.1718877424.150f2b8e107a0f4399671dcf2b1e3e2ac78252a26c9626abf4a29a77464a96c1'
appId = signature_header.split('.')[0]
timestamp = signature_header.split('.')[1]
signature = signature_header.split('.')[2]
sign_body = request_body + timestamp
if (signature == hmac.new(secret.encode('utf-8'), sign_body.encode('utf-8'), hashlib.sha256).hexdigest()):
    print("DingRTC-Signature is valid")
else:
    print("DingRTC-Signature is invalid")

回调消息列表

本文档的JSON示例省略了Body中eventIdnotifyTime

重要

回调内容可能会增加字段,或者调整字段顺序,请根据您使用的开发语言采用适当的解析方式。

验证事件

001 回调验证

该场景仅在控制台设置具体回调URL或者手动校验时触发。

{
  "eventType": "001",
  "eventData":{
    "appId": "12adxxxx2"
  }
}

频道事件

101 频道开始

{
  "eventType": "101",
  "eventData":{
    "channelId": "room**" 				// 频道id
    "timestamp": 1709696165584		// 发生时间(ms)
  }
}

102 频道结束

{
  "eventType": "102",
  "eventData":{
    "channelId": "room**" 				// 频道id
    "timestamp": 1709696165584		// 发生时间(ms)
  }
}

103 用户加入

{
  "eventType": "103",
  "eventData":{
    "channelId": "room**" 				// 频道id
    "user":{
      "userId":"123444" 
    }
    "timestamp": 1709696165584		                // 发生时间(ms)
  }
}

104 用户离开

{
  "eventType": "104",
  "eventData":{
    "channelId": "room**" 				// 频道id
    "reasonCode": 20003001,                             // 用户离开原因,详见下方错误码
    "user":{
      "userId":"123444" 
    }
    "timestamp": 1709696165584		                // 发生时间(ms)
  }
}

推流事件

1000 开始推流

{
    "eventType": "1000",
    "eventData": {
        "channelId": "room**",			// 频道id
        "liveState":{
          "code": 20000000,					//状态码,见最后状态码表格
        },  
        "taskId": "task-03061",			// 任务id
        "timestamp": 1709737037688	// 发生时间(ms)
    }
}

1001 推流正常结束

{
    "eventType": "1001",
    "eventData": {
        "channelId": "room**",			// 频道id
        "liveState":{
          "code": 20000000,					//状态码,见最后状态码表格
        },
        "taskId": "task-03061",			// 任务id
        "timestamp": 1709737037688	// 发生时间(ms)
    }
}

1002 推流异常

{
    "eventType": "1002",
    "eventData": {
        "channelId": "room**",			// 频道id
        "liveState":{
          "code": 50001001,					//状态码,见最后状态码表格
        }  
        "taskId": "task-03061",			// 任务id
        "timestamp": 1709737037688	// 发生时间(ms)
    }
}

录制事件

2000 开始录制

{
    "eventType": "2000",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "bucket":"rtc*******",              // 录制文件存放bucket
            "vendor":1,                         // 对象存储供应商,见开启录制接口
            "region":1,                         // 对象存储region,见开启录制接口 
            "startTs":1709737037688,            // 录制开始时间戳,单位:毫秒(ms)
            "code": 20000000									//录制文件总数
        },
        "taskId": "task-0422",
        "timestamp": 1709737037688
    }
}

2001 录制成功

{
    "eventType": "2001",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "bucket":"rtc*******",              // 录制文件存放bucket
            "vendor":1,                         // 对象存储供应商,见开启录制接口
            "region":1,                         // 对象存储region,见开启录制接口 
            "startTs":1709737037688,            // 录制开始时间戳,单位:毫秒(ms)
            "code": 20000000,								//状态码,见最后状态码表格
            "fileFailCount": 0,
            "fileInfo": [
                {
                    "fileDuration": 7859,  	//录制文件时长,单位:毫秒(ms)
                    "fileSize": 216777,    	//录制文件大小,单位:字节(Byte)
                    "filePath": "record/v980**/65e82ef000210**/1709737028486_1709737030532/1709737028486-1709737030532.mp4",
                    "status": 0,           	//0表示成功, 其他表示失败
                    "timestamp": 1709737037679	//录制文件生成的时间戳(ms)
                }
            ],
            "fileCount": 1									//录制文件总数
        },
        "taskId": "task-03061",
        "timestamp": 1709737037688
    }
}

2002 录制失败

{
    "eventType": "2002",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "bucket":"rtc*******",              // 录制文件存放bucket
            "vendor":1,                         // 对象存储供应商,见开启录制接口
            "region":1,                         // 对象存储region,见开启录制接口 
            "startTs":1709737037688,            // 录制开始时间戳,单位:毫秒(ms)
            "reason": "WritePlaylist failed",
            "code": 50002001,													//状态码,见最后状态码表格
            "fileFailCount": 2,
            "fileInfo": [
                {
                    "reason": "write flv file fail",	//失败原因
                    "status": 50002001,
                    "timestamp": 1709721091674
                },
                {
                    "reason": "WritePlaylist failed",
                    "fileDuration": 30437,
                    "fileSize": 123875456,
                    "filePath": "taskidtaskId-199-cid65e844**e000000001ac0000/playlist.m3u8",
                    "status": 50002001,
                    "timestamp": 1709721103666
                }
            ],
            "fileCount": 2
        },
        "taskId": "taskId-199",
        "timestamp": 1709721103673
    }
}

2010 录制服务状态变化

说明

该事件默认不会主动回调,请通过控制台或者OpenAPI 完成订阅。

{
    "eventType": "2010",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "bucket":"rtc*******",              // 录制文件存放bucket
            "vendor":1,                         // 对象存储供应商,见开启录制接口
            "region":1,                         // 对象存储region,见开启录制接口 
            "startTs":1709737037688,            // 录制开始时间戳,单位:毫秒(ms)
            "code": 20002002                    // 具体意思,请见下方状态码
        },
        "taskId": "taskId-199",
        "timestamp": 1709721103673
    }
}

2011 录制音频流变化

说明

该事件默认不会主动回调,请通过控制台或者OpenAPI 完成订阅。

{
    "eventType": "2011",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "streamChangeInfo": {            // 流变化信息
                "streamType": 3,             // 流类型 1: 摄像头流 2:共享流 3: 音频合流 4: 视频合流
                "state": 1,                  // 录制收流状态 1: 正在接收 2: 未在接收
                "direction": 2,              // 流方向 1: 输入 2: 输出
                "timestamp": 1721112755076   // 状态变化Unix 毫秒时间戳
            }
        },
        "taskId": "taskId-199",
        "timestamp": 1709721103673
    }
}

2012 录制视频流变化

说明

该事件默认不会主动回调,请通过控制台或者OpenAPI 完成订阅。

{
    "eventType": "2012",
    "eventData": {
        "channelId": "room**",
        "recordState": {
            "streamChangeInfo": {            // 流变化信息
                "uid": "user1",              // 流方向为输出时,流属于合流,uid为空。否则为具体uid
                "streamType": 1,             // 流类型 1: 摄像头流 2:共享流 3: 音频合流 4: 视频合流
                "state": 1,                  // 录制收流状态 1: 正在接收 2: 未在接收
                "direction": 1,              // 流方向 1: 输入 2: 输出
                "timestamp": 1721112755076   // 状态变化Unix 毫秒时间戳
            }
        },
        "taskId": "taskId-199",
        "timestamp": 1709721103673
    }
}

状态码表格

类型

状态码

说明

公共

20000000

成功

50000000

服务器内部错误

推流

50001001

推流异常

录制

50002001

写入用户存储失败,

可能是网络问题

50002002

启动用户存储失败,

可能是入参AK/SK/Bucket/Region/Vendor输入错误

50002003

录制时间过短,没有生成录制文件

50002004

用户存储密钥错误

50002005

bucket不存在

50002006

访问用户存储被拒绝

20002001

没有开始云端录制

20002002

云端录制初始化完成

20002003

录制组件开始启动

20002004

录制组件启动完成

20002005

停止录制

20002006

上传组件已启动

20002007

已成功上传第一个文件

用户

20003001

客户端主动退会

20003002

客户端保活异常

20003003

用户被踢出

20003004

相同uid移除

20003005

未知原因退会