# 注册回调
通过注册回调,可以订阅会议、会议室、通讯录的数据变更事件,服务器在服务数据发生变更的时候通过回调方式通知业务方。一个企业只能注册一个接收回调的url。
注册流程如下:
请求方式: POST(HTTPS)
请求地址: https://gateway.maxhub.com/hook/api/v1/client/hooks/actions/register
请求body:
{
"company_code": "000111333",
"url": "https://www.example.com/callback",
"token": "wrdolYCN8nM0",
"encrypt_key": "RUt5eZGDz3tM28qmeHSVsRwoUCa4NuviP2VknMmE0kJ",
"event_tags": [
"meeting_create",
"meeting_update",
"meeting_delete"
]
}
参数说明:
参数 | 必须 | 说明 |
---|---|---|
company_code | 是 | 企业编码,即企业管理后台显示的企业ID (登录企业管理后台,在首页可以看到) |
url | 是 | 事件回调的接口路径,必须是合法的url |
token | 是 | 3-32位长度的由字母或数字组成的字符串,用于校验消息合法性 |
encrypt_key | 是 | 43位长度的由字母或数字组成的字符串,用于加解密消息 |
event_tags | 是 | 需要订阅的事件,可以是事件类型,也可以是事件分组,填了事件分组,即订阅该分组下的全部事件 |
返回结果:
{
"message": "ok"
}
参数说明:
参数 | 说明 |
---|---|
message | 固定值ok |
# 校验回调路径
注册回调时,MAXHUB服务器会向回调路径发起POST请求,只有回调路径在5秒内返回正确结果才能注册成功,否则注册失败。业务方可以通过校验signature合法性,以及data解密后的内容是否正确,来验证回调数据是否正确。如果校验正确,业务方需要在response的body中返回新生成的signature。
回调注册成功后,事件回调的返回参数、加解密方式、签名校验方式跟这里描述的一样,后面不在赘述。唯一不同的是,data解密出来的内容不同,在具体的【事件回调】章节中会详细说明。
POST请求的body参数:
{
"nonce": "8iyBhg4q",
"timestamp": 1602317904000,
"data": "QKw5S2xCLQ276c95HhJNvPkY+8IecD3bKwfFmi/DLk/292+90/H0O1bi12/0dGWM",
"signature": "613817568cc8aa6a1ea6c1e6945296f5a95e1473"
}
参数说明:
参数 | 说明 |
---|---|
nonce | 随机字符串 |
timestamp | 时间戳 |
data | 经过AES加密,并转成base64字符串的回调数据 |
signature | 消息签名 |
data解密后的消息内容:
{
"event_type": "check_url",
"message": {
"_id": "8b2edc1f-a869-4d87-ae9f-1beec7a0c513",
"_timestamp": 1602742001287
}
}
参数说明:
参数 | 说明 |
---|---|
event_type | 事件类型,对于回调路径的校验,此为固定值check_url |
message | 事件内容,对于回调路径的校验,此为固定值{} |
— _id | 事件ID |
— _timestamp | 事件发生时的时间戳 |
业务方需要返回新生成的signature:
signature=sha1("nonce=xxx&token=xxx")
signature的生成方式参考:生成signature,但此处生成signature只需要nonce和token两个字段。业务方务必返回正确的signature,才能注册成功。
{
"signature": "5c01a87d5832f1fd7d176dfc2c0abbdc899ab0f8"
}
# data的加解密
data=base64_encode(aes(消息内容, base64_decode(encrypt_key+"=")))
- 消息内容为json格式的字符串;
- aes_key会从encrypt_key通过base64解码得来,长度为32字节
aes_key=base64_decode(encrypt_key+"=")
; - aes_key为AES加解密的密钥,AES采用CBC模式,数据采用PKCS#7填充,IV初始向量大小为16字节,取aes_key的前16字节;
加密示例:
String dataJson = "{\"event_type\":\"check_url\",\"message\":{}}";
String encryptKey = "RUt5eZGDz3tM28qmeHSVsRwoUCa4NuviP2VknMmE0kJ";
byte[] aesKey = java.util.Base64.getDecoder().decode(encryptKey + "=");
byte[] iv = new byte[16];
System.arraycopy(aesKey, 0, iv, 0, 16);
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS7Padding");
javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(aesKey, "AES");
javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] cipherText = cipher.doFinal(dataJson.getBytes(java.nio.charset.Charset.forName("UTF-8")));
String data = java.util.Base64.getEncoder().encodeToString(cipherText);
AES加密如果抛出异常:
java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding
需要添加如下jar:
<!-- 不同jdk对应的版本可能会不一样,具体版本可以在该网址中找到:https://www.bouncycastle.org/latest_releases.html -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.66</version>
</dependency>
并在代码中添加以下一行:
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
解密示例:
String data = "QKw5S2xCLQ276c95HhJNvPkY+8IecD3bKwfFmi/DLk/292+90/H0O1bi12/0dGWM";
byte[] cipherText = java.util.Base64.getDecoder().decode(data);
String encryptKey = "RUt5eZGDz3tM28qmeHSVsRwoUCa4NuviP2VknMmE0kJ";
byte[] aesKey = java.util.Base64.getDecoder().decode(encryptKey + "=");
byte[] iv = new byte[16];
System.arraycopy(aesKey, 0, iv, 0, 16);
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS7Padding");
javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(aesKey, "AES");
javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] plainText = cipher.doFinal(cipherText);
String dataJson = new String(plainText, java.nio.charset.Charset.forName("UTF-8"));
# 生成signature
signature=sha1("nonce=xxx&token=xxx")
将nonce、token(注册回调时提交的token)等字段根据字典顺序排序,再将各字段的key和value使用“=”拼接在一起,再使用“&”将各字段拼接在一起,得到待签名字符串,然后进行sha1签名,得到signature。
签名示例:
String str = "data=QKw5S2xCLQ276c95HhJNvPkY+8IecD3bKwfFmi/DLk/292+90/H0O1bi12/0dGWM&nonce=8iyBhg4q×tamp=1602317904000&token=wrdolYCN8nM0";
java.security.MessageDigest crypt = java.security.MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(str.getBytes(java.nio.charset.Charset.forName("UTF-8")));
byte[] text = crypt.digest();
final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
char[] chars = new char[2 * text.length];
for (int i = 0; i < text.length; ++i) {
chars[2 * i] = HEX_CHARS[(text[i] & 0xF0) >>> 4];
chars[2 * i + 1] = HEX_CHARS[text[i] & 0x0F];
}
String signature = new String(chars);
# 取消回调
请求方式: POST(HTTPS)
请求地址: https://gateway.maxhub.com/hook/api/v1/client/hooks/actions/unregister
请求body:
{
"company_code": "000111333"
}
参数说明:
参数 | 必须 | 说明 |
---|---|---|
company_code | 是 | 企业编码,即企业管理后台显示的企业ID (登录企业管理后台,在首页可以看到) |
返回结果:
{
"message": "ok"
}
参数说明:
参数 | 说明 |
---|---|
message | 固定值ok |