钉钉开发文档

业务事件回调

更新时间: 2019-2-14

场景

当钉钉内的企业发生一些业务变更时,会通过业务事件回调URL通知企业或者第三方应用,实现数据同步的功能。
目前支持的业务事件包含:通讯录相关事件,群会话相关事件,签到相关事件,审批相关事件。
此回调URL和关注的事件,开发者可以通过调用接口注册。

注意事项

业务事件回调目的是企业业务数据通知和同步,和第三方应用设置的应用回调地址无关,但是签名和加解密机制相同】

1.本文中所有的回调信息都是企业业务的变化事件。
2.对于不关心的业务事件,建议不要注册。
本文中回调的触发来自于企业业务事件的发生,若一个第三方应用关注了很多企业的很多业务变更类型,您应用的压力可能会比较大,适当减少不关心的业务变更类型有利于减小回调压力。
3.回调接收失败的数据仅保留一周。一周之内可以通过【获取回调失败的结果】接口拉取。

准备工作

在使用回调接口之前,需要准备好以下事项:

  1. 【注册事件回调接口】需要的接收推送的URL地址。
  2. 【注册事件回调接口】中的Token,用来生成signature,以及用来和回调参数中的signature比对,校验消息的合法性。
  3. 【注册事件回调接口】中填写的数据加密密钥(ENCODING_AES_KEY)。用于回调数据的加解密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,可以随机生成。

接收回调消息

钉钉服务器会向注册的回调url地址推送事件变更。

假设回调URL是 https://127.0.0.1/corp_biz_callback那么在钉钉服务器每一次访问回调URL时将POST请求下面这个URL(链接中的signature, timestamp, nonce的值只是示例,并不代表真实的请求参数):

https://127.0.0.1/corp_biz_callback?signature=111108bb8e6dbce3c9671d6fdb69d15066227608&timestamp=1783610513&nonce=380320111

参数说明:

参数 说明
signature 钉钉的加密签名,signature结合了开发者填写的token、请求中的timestamp、nonce参数、加密的消息体
timestamp 时间戳
nonce 随机数
Post Body { "encrypt":"1ojQf0N..." } 回调消息体使用Post请求body格式传递

返回值示例:

{
    "encrypt":"1ojQf0NSvw2WPvW7LijxS8UvISr8pdDP+rXpPbcLGOmIBNbWetRg7IP0vdhVgkVwSoZBJeQwY2zhROsJq/HJ+q6tp1qhl9L1+ccC9ZjKs1wV5bmA9NoAWQiZ+7MpzQVq+j74rJQljdVyBdI/dGOvsnBSCxCVW0ISWX0vn9lYTuuHSoaxwCGylH9xRhYHL9bRDskBc7bO0FseHQQasdfghjkl"
}

回调消息体解密

上面的encrypt字段是经过加密的消息体,encrypt经过解密后,才能拿到回调通知中的数据,服务商可以直接使用钉钉提供的库进行解密的处理。

另外,接收到推送数据之后,需要返回字符串success (代表了你收到了推送),返回的数据也需要做加密处理,如果不返回,钉钉服务器将持续推送下去,达到一定阈值后将不再推送。

示例代码:

/**对encrypt进行解密**/
DingTalkEncryptor dingTalkEncryptor = null;
String plainText = null;
try {
    dingTalkEncryptor = new DingTalkEncryptor(Env.TOKEN, Env.ENCODING_AES_KEY, Env.CORP_ID);
    String encrypt = json.getString("encrypt");
    plainText = dingTalkEncryptor.getDecryptMsg(msgSignature, timeStamp, nonce, encrypt);
} catch (DingTalkEncryptException e) {
    // TODO Auto-generated catch block
    System.out.println(e.getMessage());
    e.printStackTrace();
}
 
/**对从encrypt解密出来的明文进行处理**/
JSONObject plainTextJson = JSONObject.parseObject(plainText);       
String eventType = plainTextJson.getString("EventType");
switch (eventType){
case "org_user_add"://do something
    break;
case "org_user_modify"://do something
    break;
case "org_user_leave":// do something
    break;
case "check_url"://do something
default : //do something
    break;
}
 
/**对返回信息进行加密**/
long timeStampLong = Long.parseLong(timeStamp);
Map<String,String> jsonMap = null;
try {
    jsonMap = dingTalkEncryptor.getEncryptedMap("success", timeStampLong, nonce);
} catch (DingTalkEncryptException e) {
    System.out.println(e.getMessage());
    e.printStackTrace();
}
JSONObject json = new JSONObject();
json.putAll(jsonMap);   
response.getWriter().append(json.toString());

加解密库和demo的下载:

JAVA版本DEMO见本节代码 JAVA版本加密库
PHP版本DEMO PHP版本加密库
C#版本DEMO C#版本加密库

注意:请仔细阅读DEMO中的README

明文msg的加密过程

msg_encrypt = Base64_Encode( AES_Encrypt[random(16B) + msg_len(4B) + msg + $key] )
AES加密的buf由16个字节的随机字符串、4个字节的msg长度、明文msg和$key组成。其中msg_len为msg的字节数,网络字节序。

  • $key对于ISV来说,填写对应的suitekey。
  • $key对于普通企业开发,填写企业的CorpId。

加密方案对应的解密方案

  • 取出返回的JSON中的encrypt字段。
  • 对密文BASE64解码:aes_msg=Base64_Decode(encrypt)。
  • 使用AESKey做AES解密:rand_msg=AES_Decrypt(aes_msg)。
  • 验证解密后$key、msg_len,当为第三方套件回调事件时,CorpId的内容为suitekey。
  • 去掉rand_msg头部的16个随机字节,4个字节的msg_len,和尾部的$CorpId即为最终的消息体原文msg。
以上内容是否对您有帮助:
在文档使用中是否遇到以下问题(多选):
  • 内容错误
  • 更新不及时
  • 链接错误
  • 缺少代码/图片示例
  • 太简单/步骤待完善
手机号
更多建议
提交成功,感谢您的反馈!