多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

钉钉自定义机器人Webhook接入指南:从创建到发送第一条告警消息

钉钉自定义机器人Webhook接入指南:从创建到发送第一条告警消息 最近在处理监控群里的告警通知我又把钉钉自定义机器人整个流程重新捋了一遍。这个名字听起来挺技术但本质特别朴素你在钉钉群里创建一个机器人系统给你一个Webhook URL之后任何服务只要向这个URL发一条POST请求消息就能出现在群里。很多人卡在第一步要么找不到创建入口要么拿到URL之后不知道签名参数怎么拼甚至有人复制URL时把access_token漏掉了折腾半天发不出去。这篇教程就把整个流程从头拆开怎么新建机器人、完整Webhook地址从哪获取、URL里每个参数什么意思、拿到之后怎么发第一条消息最后把常见报错也整理成了排查清单。适合第一次接触钉钉开放能力的同学也适合运维和开发想快速把告警、CI通知接进群里的人。1. 为什么要用自定义机器人先想清楚需求1.1 自定义机器人最常干的几件事我见到最多的用法是做告警推送。比如服务器磁盘快满了、数据库慢查询变多、某个微服务接口失败率超过阈值这些信息需要第一时间同步到群里而不是等人主动去翻监控面板。自定义机器人就是现成的消息出口服务端脚本直接把告警内容POST过去群成员立刻就能在聊天窗口看到省去了反复登录监控平台的功夫。还有一类是构建和发布通知。代码仓库有新提交、流水线构建完成、版本发布成功或失败机器人可以把分支名、提交人、状态、日志链接整理成一条消息发到技术群里。自动化流程跑完不需要再人工盯终端群消息就是最直观的结果展示。定时任务提醒、每日巡检报表这类场景也很常见。把机器人当消息网关用调度任务去触发发送一个机器人能对接多个任务只要在消息里区分内容就行。另外如果启用了Outgoing回调机制群里成员机器人还能把消息转发到指定服务机器人就从“单方向推送”变成“能互动”不过那部分需要另配公网回调地址本文先聚焦最基础的消息推送流程。1.2 为什么它是“轻量接入”的最优解钉钉的消息机器人其实分好几种真正适合快速接入的是自定义机器人。它不需要注册一个完整的企业内部应用不需要申请AppKey、AppSecret那套复杂权限更不用在服务端部署回调程序。你只需要在群聊里创建一次机器人保存好Webhook URL和密钥剩下的事情就是一个HTTP调用。对比企业内部应用机器人自定义机器人的定位就是“轻”。企业内部应用机器人通常要上报应用信息、配置权限范围、走审核流程适合需要深度集成会话、组织架构或用户身份的复杂场景。但如果你的需求只是“把一条告警发进群里”用自定义机器人就够了。它把复杂度收敛在群聊和Webhook这一层出了问题最多影响群消息不会牵连整个应用体系。选择自定义机器人之前只需要确认两点一是这个群确实存在且你能进入群设置的管理入口二是发送方脚本、服务器、定时任务能访问公网因为消息是通过HTTPS发到钉钉服务器的。满足这两点就可以继续往下走了。2. 创建之前的准备工作2.1 前置条件清单开始创建之前先把基础条件列一下。这个清单很短但每一样都别漏。一个钉钉账号且是你目标群的群成员。你要能打开这个群的设置页面如果看不到“机器人”入口大概率是群管理员限制了成员配置权限找群主确认一下。一个能发起HTTPS请求的测试环境。终端、本地电脑、一台服务器都可以后文我会给出curl和Python两种方式。准备一个合适的机器人名称。名称在群聊中会直接显示建议用“运维告警”“构建通知”这种直观名字方便群里成员一眼看出消息来源。先决定安全校验方式。这一步很多人会忽略等创建完再纠结后面的流程里我会专门解释三种方式的差别。建议先想好避免来回改。提示创建机器人不需要写代码也不需要下载SDK所有操作都在钉钉客户端界面里完成。代码是拿到Webhook之后才需要的东西。2.2 安全设置先想好创建时不慌钉钉在创建自定义机器人时会要求配置安全设置目的很明确防止有人拿着Webhook地址往群里灌垃圾消息。安全设置有三种可以只选一种也可以组合使用。第一种是“自定义关键词”。你在机器人里设置一个关键词以后这个机器人发出的每一条消息文本内容里都必须包含该关键词否则消息会被拒绝。这个实现最简单不用算签名但限制也最明显消息内容不能太自由。第二种是“加签”。系统会返回一个以SEC开头的密钥发送方需要用这个密钥和时间戳算出签名把签名拼在URL里钉钉服务器校验通过才会接受消息。这个方式对消息内容没有额外限制安全性也高前提是发送端要能执行签名算法。我通常会优先推荐加签。第三种是“IP地址段”。你可以填一个或一组服务器的出口IP只有来自这些IP的请求才会被放行。这个模式适合所有流量都从固定IP出去的服务器环境但如果发送端换IP消息就会发送失败维护成本要看网络环境而定。我的建议是如果发送端是你们自己的服务器用加签最稳妥如果是临时脚本、个人电脑调试用关键词最省事如果公司网段固定又有运维团队维护IP白名单那直接选IP段也行。三种方式的原理会在第4章再展开。3. 新建自定义机器人完整流程3.1 从群设置进入机器人管理登录钉钉客户端打开目标群聊窗口。在右上角找到“...”菜单进入“群设置”页面一般在功能列表里能找到“机器人”。不同客户端版本入口位置略有差异但大方向一致群设置 - 机器人 - 添加机器人。我这里以群内创建自定义机器人的路径来写这也是大多数人实际会走的路。如果你用的是新版客户端群设置里可能直接显示“群机器人”入口点进去就有“添加机器人”按钮。这个操作需要你有一定群权限通常群成员就能操作但部分企业群开启了管理限制入口可能置灰必要时让群主在管理后台调整。3.2 创建机器人的配置项点击“添加机器人”后页面会出现机器人类型列表。选择“自定义”类型一般显示为“自定义通过Webhook接入自定义服务”有些旧版本叫“自定义机器人”。接下来填写基础信息。机器人的名称是必填项最终会显示在群成员列表和消息发送者位置。名称建议和用途挂钩比如“构建通知-开发群”“生产环境告警”这样当群里同时存在多个机器人时不会混淆。头像可以自定义上传也可以先用默认头像这一步不关键后续可以在机器人管理页面修改。最关键的一步是设置“安全设置”。这个页面通常会把三种方式放在一起展示自定义关键词、加签、IP地址段。选择加签时点击添加按钮后系统会生成一个加签密钥以SEC开头选择关键词时在输入框里填一个或多个关键词选择IP地址段时按格式填写IP多个IP用换行分隔。这里有一个容易忽略的细节钉钉要求“至少选择一种安全设置”但如果你选了“自定义关键词”并且设置了多个关键词消息正文只要包含任意一个即可不需要同时包含全部关键词。3.3 拿到Webhook URL与加签密钥配置完成并勾选服务协议后点击“完成”系统会展示机器人的Webhook地址。这是一个完整的URL形如https://oapi.dingtalk.com/robot/send?access_tokenxxxxxxxxxxxxxxxx这个URL就是所谓的“机器人URL”也是后面所有操作的起点。它包含了机器人的访问令牌后续消息请求都要在这个URL基础上做扩展。如果你选择的是“加签”模式同一个页面还会显示对应的加签密钥也就是前面提到的SEC开头的字符串。这个密钥不会在群里展示只在创建成功后的配置页面展示之后可以到机器人管理页面查看或重置。建议在完成这一步时立刻做两件事第一把Webhook URL完整复制下来注意别漏掉access_token这一整段参数第二如果使用加签把SEC密钥复制到你们自己的配置中心、环境变量或密码管理器里不要直接贴在聊天窗口或公共文档里。3.4 创建后的管理入口创建完成后回到群设置中的“机器人管理”页面这个机器人的名称、头像、Webhook地址等都会列在列表里。如果忘记保存Webhook不用重新创建直接进入机器人详情页就能重新复制。在管理页面还可以做几件重要的事查看URL、重置加签密钥、停用或删除机器人。如果某一天Webhook泄露了或者怀疑有外部消息混进来最快的处理方式就是重置密钥或删除重建而不是只改关键词。机器人被删除后旧URL会立即失效群里再发消息会提示机器人无效需要重新添加。注意Webhook URL不是“群邀请链接”它只是消息推送入口。拿到URL的人不需要加入你的群照样能往群里发消息所以一定要保管好。4. Webhook URL拆解每个参数都是什么意思4.1 access_token机器人的身份凭证拿到手的URL看起来很简单其实最核心的是access_token参数。它相当于这个机器人专属的“主钥匙”钉钉服务端根据这个token判断消息要投递到哪个群的哪个机器人。一个群可以添加多个机器人每个机器人的access_token各不相同它们接收消息后都会独立展示在群里。发送消息时不管使用哪种安全校验方式请求地址都必须带access_token否则钉钉无法识别目标机器人。这个参数直接拼在URL的query部分不需要放在HTTP请求体里。有些同学在复制URL时只会复制域名部分导致后面拼接签名时怎么调都不对排查第一步就应该确认access_token是否完整。4.2 timestamp与sign加签模式的签名算法当机器人选择“加签”安全方式时请求地址会在access_token之外额外带上两个参数timestamp和sign。timestamp是当前请求的毫秒级时间戳注意是毫秒不是秒用字符串形式。钉钉服务器会用它来防止旧请求被重放时间偏差过大或签名不匹配都会拒绝请求。sign的计算过程不难但细节容易出错。官方推荐的步骤是把当前毫秒时间戳字符串和加签密钥拼接成待签名字符串timestamp \n secret使用HmacSHA256算法对这段字符串做哈希密钥使用上面的secret对哈希后的二进制结果做Base64编码对Base64结果再做URL编码得到最终的sign参数拼接后的请求URL大概长这样https://oapi.dingtalk.com/robot/send?access_tokenxxxxtimestamp1699999999999signxxxxxxxx服务器收到请求后会用相同的timestamp和secret重新计算签名比对结果是否一致。整个过程相当于双方用同一个密钥做了一次安全握手密钥没有在网络中明文传输所以安全性比关键词方式高一个档次。4.3 三种安全校验方式怎么选我把三种方式放在一张表里对比这样选择起来更直观。安全方式实现难度对消息内容的限制适合场景自定义关键词最简单无额外计算每条消息必须包含关键词个人调试、简单提醒、消息模板固定加签需要写签名代码无内容限制生产环境告警、服务器脚本IP地址段无需签名仅校验来源IP无内容限制固定出口IP的服务器环境从安全强度上讲加签和IP地址段明显高于关键词。关键词方式更多是“风险提示”因为一旦Webhook泄露任何能构造含关键词消息的人都可以发送请求所以它防止的是误发而不是恶意调用。加签方式在密钥未泄露的前提下即使别人拿到Webhook URL也算不出合法的sign消息依然发不进去。如果你担心团队成员签名代码写不对可以先选“自定义关键词”跑通整条链路之后再升级到加签。两种方式也可以组合使用具体校验规则以客户端提示和官方文档为准。实际使用时我建议只选一种核心方式避免排查时搞混。5. 实操环节用获取到的URL发送第一条消息5.1 先用curl快速验证连通性拿到Webhook URL后第一件事不是写复杂脚本而是先用一条最简单的HTTP请求验证连通性。终端里执行下面这条命令记得把access_token换成你自己的curl https://oapi.dingtalk.com/robot/send?access_token你的token \ -H Content-Type: application/json \ -d {msgtype:text,text:{content:钉钉机器人测试}}如果机器人设置了“自定义关键词”content字段里必须包含所设置的关键词比如关键词是“测试”那么消息内容里就要带着“测试”两个字。请求成功时返回的JSON里会有errcode:0群里马上能看到消息如果返回的errcode不是0errmsg会给出具体原因比如关键词不匹配或者token无效。如果机器人选择了“加签”就不能直接用上面的原始URL发了需要在URL里额外拼上timestamp和sign。在Linux或macOS终端里可以用openssl生成签名timestamp$(date %s%3N) secretSEC你的密钥 string_to_sign${timestamp}\n${secret} sign$(printf ${timestamp}\n${secret} | openssl dgst -sha256 -hmac ${secret} -binary | base64) sign_encoded$(python3 -c import urllib.parse,sys; print(urllib.parse.quote_plus(sys.argv[1])) ${sign}) curl https://oapi.dingtalk.com/robot/send?access_token你的tokentimestamp${timestamp}sign${sign_encoded} \ -H Content-Type: application/json \ -d {msgtype:text,text:{content:服务异常告警CPU使用率超过90%}}注意第三行printf里的\n是换行符不是字面字符。sign计算出来后我在示例里用Python做了URL编码这一步容易被忽略Base64结果里可能出现、/、如果直接拼到URL里在query中会被解析成空格钉钉服务端解出来的签名就和计算值不一致导致“sign not match”报错。5.2 用Python脚本封装一个发送函数curl验证通过后我建议再写一个小的Python发送函数方便后续接监控告警、定时任务或CI脚本。用requests库实现代码很简短import time import hmac import hashlib import base64 import requests from urllib.parse import quote_plus WEBHOOK https://oapi.dingtalk.com/robot/send ACCESS_TOKEN 你的token SECRET SEC你的密钥 # 加签模式才需要 def send_text(content): timestamp str(round(time.time() * 1000)) string_to_sign f{timestamp}\n{SECRET} hmac_code hmac.new( SECRET.encode(utf-8), string_to_sign.encode(utf-8), digestmodhashlib.sha256 ).digest() sign quote_plus(base64.b64encode(hmac_code).decode(utf-8)) url f{WEBHOOK}?access_token{ACCESS_TOKEN}timestamp{timestamp}sign{sign} payload { msgtype: text, text: {content: content} } resp requests.post(url, jsonpayload) print(resp.json()) if __name__ __main__: send_text(Python机器人发送测试)如果不需要加签直接把SECRET去掉URL里也不用拼timestamp和signrequests库只需要把payload POST过去就行。这段代码里我用了hmac、hashlib、base64三个标准库加requests一个第三方库没有额外依赖。写的时候有一点经验想分享不要把ACCESS_TOKEN和SECRET硬编码在源码文件里尤其当脚本会被提交到代码仓库时。可以把它们放在环境变量里或者读取本地配置文件避免密钥随代码库流传。5.3 再扩展几种常用消息格式钉钉自定义机器人的消息格式不只text一种常用的还有link、markdown和actionCard。link类型适合带跳转链接的通知比如发布公告、版本说明。它的JSON结构类似{ msgtype: link, link: { text: 本次版本更新了跨部门审批流程具体说明见链接。, title: 产品发布通知, picUrl: , messageUrl: https://example.com/release } }markdown类型很适合做巡检报告、构建结果、更丰富的告警文本。钉钉支持一部分Markdown语法比如标题、加粗、列表、链接但不是所有语法都支持。示例{ msgtype: markdown, markdown: { title: 巡检报告, text: ### 巡检结果\n- 服务A正常\n- 服务B延迟偏高\n- [查看完整报告](https://example.com/report) }, at: { isAtAll: false } }如果需要在告警时群里所有人可以在at里设置isAtAll: true。但要注意这个操作在一些群里需要机器人有对应权限否则会发送失败。actionCard类型适合把一条通知做成带操作按钮的卡片用于二次确认类场景feedCard类型可以一次展示多条图文适合资讯流。业务需求不复杂时text和markdown基本够用了。6. 常见报错与排查思路6.1 签名计算中的高频错误“sign not match”是我见到最多的报错。遇到这个提示先别急着怀疑代码按顺序检查几个点时间戳是不是毫秒级字符串签名字符串里是不是timestamp \n secret注意这个换行符secret有没有多复制或者少复制字符Base64之后有没有做URL编码。还有一个容易踩的坑在Python里如果用了hmac.new(bytes, bytes, hashlib.sha256)第一个参数密钥和第二个参数待签名内容都应该以bytes形式传入。很多人习惯传字符串写的时候忘了加encode(utf-8)算出来的sign完全不对。我前面给的示例代码是验证过的可以对比自查。6.2 Webhook本身的问题如果报错信息是“token is not found”或者“robot code is invalid”问题通常出在Webhook URL本身。“token is not found”说明access_token参数缺失或错误最常见的是复制URL时只复制了一部分或者URL地址里带了看不见的空格。检查时不要用眼睛扫直接重新完整复制一次。“robot code is invalid”表示这个机器人已经不存在或Webhook已失效常见原因包括机器人被删除、群被解散、密钥被重置。解决方式是回到群设置重新添加机器人拿到新的Webhook URL并同步更新脚本里的配置。6.3 消息内容触发的限制如果你设置了“自定义关键词”但发送时消息内容里没有包含关键词钉钉会拒绝请求并提示关键词不在消息内容中。这里要注意关键词匹配的是消息的文本部分text类型看content字段markdown类型看text字段link类型看text字段。关键词是“包含”关系不是“完全相等”。比如设置了关键词“监控”消息内容“监控告警”是可以通过的因为包含了“监控”但如果只设置“监控告警”消息内容只写“监控”那就匹配不上因为“监控”里不包含完整的“监控告警”。另外消息内容如果包含明显违规或垃圾信息也会被内容安全策略拦截。这个属于前置红线接入之前自己先过滤掉就行没必要去挑战钉钉的内容校验。6.4 几个容易被忽略的细节把实际使用中容易翻车的地方集中列一下发送频率同一机器人的消息发送频率过高可能被限制特别是多个任务同时推送时。建议在服务端做批量合并比如多个告警凑成一条Markdown发出去而不是一秒内疯狂POST几十次。配置变更Webhook和密钥在机器人的管理页面都可以修改修改后旧URL立即失效。如果今天折腾半天没成功明天又好了先想想是不是有人改过安全设置。网络环境钉钉的接口服务器需要能从公网访问如果你的服务器或测试机在受限网络里发不出去先去检查HTTPS出口而不是对着代码反复调。日志留存发送结果的返回体别只print线上建议记录到日志文件这样排查“为什么某条告警没发出来”时能快速定位是参数问题、网络问题还是内容问题。7. 我的一些实操体会最后分享几个实际经验。签名部分是最值得花时间先跑通的地方。我第一次写发送脚本的时候犯过一个特别低级的错误把时间戳取了秒级以为只要在服务器允许的偏差范围内就行结果反复报错。后来养成了习惯凡是接钉钉加签模式第一行写timestamp str(round(time.time() * 1000))先固定成毫秒后面再也没出过问题。另一个经验是Webhook和密钥的管理。我见过团队把Webhook直接写进分享给别人的在线文档里结果没过多久群里就开始出现陌生消息。真的不要在公共渠道暴露这种信息。稍微稳一点的做法是把Webhook和SECRET放到配置中心或环境变量里脚本启动时读取这样既不会落在代码仓库也方便在泄露时统一替换。还有一点如果你用“自定义关键词”安全方式可以考虑让消息内容固定带一个项目代号比如统一以[ops]开头。这样即使别人猜到了关键词你的消息内容也有一定辨识度。不过更推荐的还是加签密钥不出去安全边界就还在自己手里。如果你们团队有多个机器人、多条推送链路我建议做一个小工具统一封装发送动作内部管理Webhook列表和密钥暴露给外部就是一个简单的“发送到某群”函数。接监控、接CI、接定时任务都会变得很轻这个投入非常值得。等这套轮子跑起来后面接什么通知无非就是再封装一个发送函数的事。
返回列表