# 自定义机器人指南

# 一、功能场景

企业存在给 特定群组自动推送消息的需求,比如:监控报警推送、销售线索推送、运营内容推送等。
你可以在群聊中添加一个 自定义机器人,通过服务端调用 webhook 地址,即可将外部系统的通知消息即时推送到群聊中。我们也提供了 自定义关键词IP白名单签名 三种维度的安全配置,控制 webhook 的调用范围。

注意:

  • 你需有 一定的服务端开发基础,通过请求调用 自定义机器人webhook地址,实现消息推送功能。
  • 自定义机器人 添加进群后即可使用,不需租户管理员审核。这提升了开发机器人的便捷性,但出于租户数据安全考虑,也限制了 自定义机器人 的使用场景。 自定义机器人 不具有 任何数据访问权限。
  • 如果希望实现机器人群管理、获取用户信息等深度的应用场景,建议你参考 开发教程,通过 机器人应用 实现。 自定义机器人机器人应用 的能力对比参考如下:
能力 自定义机器人 机器人应用
向所在群推送消息
实现包含链接跳转的消息卡片
实现将点击操作提交到服务端的消息卡片
响应用户@机器人的消息
向用户发送单聊消息
创建群、管理群、获取群信息
访问通讯录、管理云文档等其他丰富的开放能力

# 二、操作流程

# 第一步:邀请自定义机器人入群

进入您的目标群聊,打开 聊天设置,找到 群机器人,并点击 添加机器人,选择 自定义机器人加入群聊。

为你的机器人输入一个合适的机器人名称和描述,然后点击添加

# 第二步:配置 webhook

在添加机器人后,您会获取该机器人的 webhook 地址,格式如下:

https://hi-open.zhipin.com/open-apis/bot/hook/xxxxxxxxxxxxxxxxx
1

请妥善保存好此 webhook 地址,不要公布在Gitlab、博客等可公开查阅的网站上,避免地址泄露后被恶意调用发送垃圾消息

# 第三步:调用webhook发送消息

用任意方式向该 webhook 发起 HTTP POST 请求,即可向这个自定义机器人所在的群聊发送消息。

注意:
你需要一定的服务端开发基础,通过服务端请求方式调用webhook地址。
以curl指令为例,请求示例如下:

不开启安全设置签名校验时的调用示例,开启的签名校验的示例请参照下方安全设置部分

curl -X POST -H "Content-Type: application/json" \
-d '{"msg_type":"text","content":{"text":"request example"}}' \
https://hi-open.zhipin.com/open-apis/bot/hook/xxxxxxxxxxxxxxxxx
1
2
3

你可以把上述指令复制到 macOS系统的终端应用(或Windows系统的控制台应用)中进行测试。

请将上述示例中的url示例部分

https://hi-open.zhipin.com/open-apis/bot/hook/xxxxxxxxxxxxxxxxx
1
替换为您的真实信息,若测试出错,请先检查复制的指令是否和测试指令结构一致。

请求成功,返回体为:

{
  "code": 0,
  "msg": "success",
  "data": true
}
1
2
3
4
5

请求体格式错误,返回体如下。

{
  "code": 400,
  "msg": "参数有误",
  "data": {}
}
1
2
3
4
5

请检查:

  • 请求体内容格式是否与各消息类型的示例代码一致
  • 请求体大小不能超过20k

# 三、添加安全设置

如果未妥善保管webhook地址,可能存在webhook地址泄露后,被恶意开发者调用,发送垃圾信息的风险,我们强烈建议对其进行安全设置。安全设置目前提供了 3 种方式,可根据需求选择一种及以上方式进行配置。

# 方式一:自定义关键词

最多设置 10 个关键词。设定后,只有包含至少一个关键词的消息才会被发送。
如,将自定义关键词设为应用报警和项目更新后,自定义机器人发送的消息至少包含其中一个词,才会被发送到群聊。

发送请求后,若自定义关键词校验失败,将返回以下信息:

关键词校验失败

{
  "code": 200401,
  "msg": "群安全策略校验失败"
}
1
2
3
4

# 方式二:IP 白名单

最多设置 10 个 IP 地址或地址段。设定后,只处理来自所设 IP 地址范围的请求。支持段输入,如 123.12.1.* 或 123.1.1.1/24

IP校验失败

{
  "code": 200401,
  "msg": "群安全策略校验失败"
}
1
2
3
4

# 方式三:签名校验

设定后,发送的请求是需要签名验证来保障来源可信。
签名的算法:把 timestamp + "\n" + 密钥 当做签名字符串,使用 HmacSHA256 算法计算签名,再进行 Base64 编码。

需要注意的是

  • timestamp为距当前时间不超过 1 小时(3600s)的时间戳,时间单位s,如:1599360473
  • 密钥在创建机器人时会自动生成,可直接从机器人信息界面上复制

Go语言示例代码如下:

func GenSign(secret string, timestamp int64) (string, error) {
   //timestamp + key 做sha256, 再进行base64 encode
   stringToSign := fmt.Sprintf("%v", timestamp) + "\\n" + secret
   var data []byte
   h := hmac.New(sha2New, []byte(stringToSign))
   _, err := h.Write(data)
   if err != nil {
      return "", err
   }
   signature := baseStdEncoding.EncodeToString(h.Sum(nil))
   return signature, nil
}
1
2
3
4
5
6
7
8
9
10
11
12

Java示例代码如下:

package sign;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.codec.binary.Base64;

public class SignDemo {

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
        String secret = "demosecret";
        int timestamp = 1686887084;
        System.out.printf("sign: %s", GenSign(secret, timestamp));
    }

    private static String GenSign(String secret, int timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
        //把timestamp+"\n"+密钥当做签名字符串
        String stringToSign = timestamp + "\n" + secret;
        //使用HmacSHA256算法计算签名
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return URLEncoder.encode(new String(Base64.encodeBase64(signData)));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Python示例代码如下:

import base64
import hashlib
import hmac
# 签名
def gen_sign(secret, timestamp):
    # 把timestamp+"\n"+密钥当做签名字符串
    string_to_sign = f"{timestamp}\n{secret}"
    # 使用HmacSHA256算法计算签名
    hmac_code = hmac.new(secret.encode('utf-8'), msg=string_to_sign.encode('utf-8'), digestmod=hashlib.sha256).digest()
    # 对签名数据进行Base64编码,并进行URL编码
    sign = base64.b64encode(hmac_code).decode('utf-8')
    # 对sign进行urlencode
    return urllib.parse.quote(sign, safe='')
1
2
3
4
5
6
7
8
9
10
11
12
13

获得签名后,在发送请求时需加上 timestamp(单位秒:s) 和 sign (即从机器人管理页面获得的签名密钥)字段。发送文本消息的请求例如下:

开启签名验证后发送文本消息

{
  "timestamp": "1599360473",
  "sign": "xxxxxxxxxxxxxxxxxxxxx(签名密钥)",
  "msg_type": "text",
  "content": {
    "text": "request example"
  }
}
1
2
3
4
5
6
7
8

发送请求后,若签名校验失败,请排查以下原因:

  1. 时间戳距发送时已超过 1 小时,签名已过期;
  2. 服务器时间与标准时间有比较大的偏差,导致签名过期。请注意检查、校准你的服务器时间;
  3. 签名不匹配,校验不通过。将返回以下信息:

签名校验失败

{
  "code": 200401,
  "msg": "群安全策略校验失败"
}
1
2
3
4

# 四、发送更加个性化的消息

自定义机器人添加完成后,就能向其 webhook 地址发送 POST 请求,从而在群聊中推送消息了。目前支持推送的消息格式有 文本图片消息消息卡片等。
在消息体中,参数 msg_type和对应的消息类型的映射关系如下:

参数(msg_type) 消息类型 参考文档
text 文本
compressive_card 消息卡片 新消息卡片消息

# 发送文本消息

请求的消息体示例:

{
  "msg_type": "text",
  "content": {
    "text": "新更新提醒"
  }
}
1
2
3
4
5
6

# 发送消息卡片

消息卡片可由按钮等多种组件类型搭建而成。关于消息卡片的设计规范、格式等信息,请详见开放平台文档- 消息卡片,或咨询Bosshi开放平台相关同学。

  • 自定义机器人发送的消息卡片,只支持通过按钮、文字链方式跳转url,不支持点击后回调信息到服务端的 回传交互
  • 在消息卡片中如果要@提到某用户,请注意:自定义机器人仅支持通过 open_id 的方式实现,暂不支持 email、 user_id等其他方式。
  • 在新消息卡片中如果要@提到某用户,请注意:自定义机器人支持通过 open_id emailuser_id 三种方式进行,详情参考compressive_card的语法规则

请求的消息体示例:compressiveCardContent为在消息卡片搭建平台上出的卡片json

{
  "msg_type": "compressive_card",
  "content": {
    "compressiveCardContent": "{\"modules\":[{\"tag\":\"div\",\"text\":{\"content\":\"这是新的消息卡片,现在你可以像搭建乐高一样来搭建消息卡片,同时还可以添加button、下拉交互,\n\t<at email='zenglinhong@kanzhun.com'></at><font color='red'>赶快来尝试吧</font>!\",\"tag\":\"hi_md\"}}],\"header\":{\"text\":{\"content\":\"这是webhook发送消息卡片的标题\",\"tag\":\"plain_text\"},\"color\":\"blue\"}}"
  }
}

1
2
3
4
5
6
7

# 五、删除自定义机器人

群设置 - 群机器人中,点击已有的机器人,点击右侧 删除机器人 按钮即可。

# 六、常见问题

# 如何实现@指定人、@所有人

可以在机器人发送的普通文本消息(text)中,使用at标签实现@人效果。具体请求示意如下:

请求体示意:

{
  "msg_type": "text",
  "content": {
    "text": "新更新提醒",
    "atIds": "userOpenId"
  }
}

1
2
3
4
5
6
7
8

在富文本卡片(compressive_card)中使用@指定人(暂不支持所有人)请参考文档富文本卡片语法规则 (opens new window)

# 如何获得 @指定人 时所需要的open_id?

自定义机器人不需要租户管理员审核即可向所在的群(包括外部群)发送消息。这一开发上的灵活性也限制自定义机器人不具有任何数据访问权限。否则会在管理员不知情的条件下,泄露租户的隐私信息。

基于这个前提,自定义机器人本身不能调用接口获取用户的open_id,或直接通过用户的邮箱、手机号来@人(恶意开发者可能用这种方式扫出群成员的头像、姓名等隐私信息)。但你可以开发一个机器人应用,使用以下受管控的方案获得用户的 open_id ,然后参考 怎么实现机器人@人,在自定义机器人推送的消息中@人。

方案一:通过邮箱或手机号反查用户的open_id

  • 首先,你需要 创建一个自建应用;
  • 接着,为应用申请权限:通过手机号或邮箱获取用户 ID(contact:user.id:readonly)。并创建应用版本,提交发版审核。
  • 最后,在版本发布审核通过后,调用 通过手机号或邮箱获取用户 ID接口,即可通过用户的手机号或邮箱获取用户的 open_id

方案二:解析用户发送给机器人的带@人内容的消息,获取目标用户的open_id

  • 首先,你需要 创建一个自建应用;
  • 接着,
    • 为应用申请权限:获取用户发给机器人的单聊消息(im:message.p2p_msg)、获取与发送单聊、群组消息(im:message);
    • 订阅 消息与群组 分类下的 接收消息事件;
    • 为这个自建应用创建应用版本,提交发版审核。
  • 最后,在版本审核发布后,你可以在同机器人的单聊中发送 @某某某 用户的消息。解析 接收消息事件的返回内容,其中的消息体内上报了被 @用户 的 open_id 信息。
最后更新于 : 2/2/2024, 5:34:18 PM