# 注册回调

通过注册回调,可以订阅会议、会议室、通讯录的数据变更事件,服务器在服务数据发生变更的时候通过回调方式通知业务方。一个企业只能注册一个接收回调的url。

注册流程如下:

An image

请求方式: 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+"=")))

  1. 消息内容为json格式的字符串;
  2. aes_key会从encrypt_key通过base64解码得来,长度为32字节aes_key=base64_decode(encrypt_key+"=")
  3. 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&timestamp=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