| name | dingtalk-webhook |
| description | Send messages to DingTalk (钉钉) custom robots via webhook. Handles keyword mode and sign mode (加签), Chinese JSON escaping pitfalls, and message type variants (text/markdown/action card/feed). |
DingTalk Webhook 推送
何时使用
- 给钉钉群机器人发消息(Action Card / 文本 / Markdown)
- 测试自定义机器人是否连通
- 关键词模式 / 加签模式 / IP 白名单 三种安全设置的请求构造
- 关键陷阱:内含中文的 JSON 必须用文件 payload,不能用 shell 内联
关键陷阱:中文 JSON 必须用文件
❌ 内联 --data-raw '{...中文...}' 会失败:
curl -X POST "$URL" --data-raw '{"msgtype":"text","text":{"content":"[日报] 关键词命中"}}'
✅ 用文件 + --data-binary:
echo '{"msgtype":"text","text":{"content":"广告 关键词命中"}}' > /tmp/msg.json
curl -X POST "$URL" \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @/tmp/msg.json
Node.js 替代方案(更可靠):
const fs = require('fs');
fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: fs.readFileSync('/tmp/msg.json', 'utf8')
});
关键词模式探测
当 errcode: 310000, errmsg: "关键词不匹配" 时,说明机器人开了关键词过滤:
- 先去群设置看(群 → 智能群助手 → 机器人 → 安全设置 → 自定义关键词)
- 批量探测脚本:把候选关键词写到不同文件,循环 post 看哪个返回
errcode: 0
- 可能同时启用加签:如果所有关键词都失败但确认已配置,去看是否有加签 secret
加签模式(sign mode)
签名 URL 构造:
import time, hmac, hashlib, base64, urllib.parse
def sign_url(webhook_url: str, secret: str) -> str:
timestamp = str(round(time.time() * 1000))
string_to_sign = f"{timestamp}\n{secret}"
hmac_code = hmac.new(secret.encode(), string_to_sign.encode(), digestmod=hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
return f"{webhook_url}×tamp={timestamp}&sign={sign}"
支持的消息类型
| msgtype | 用途 |
|---|
text | 纯文本 |
markdown | Markdown 渲染 |
link | 单条链接卡片 |
actionCard | 多按钮行动卡片(日报摘要最常用) |
feedCard | 多链接卡片 |
Action Card 示例(含跳转链接)
{
"msgtype": "actionCard",
"actionCard": {
"title": "KovaScape 日报 2026-07-26",
"text": "## 业绩\n销售额 $32,847 (↓3%)\n订单 1,205\n毛利 $9,856\n毛利率 30%\nTACoS 26% ⚠\n\n## 异常\nP0 × 6\n- 缺货 3 条\n- 毛利<0 1 条\n- Buybox 丢失 1 条\n- 退款率异常 1 条",
"singleTitle": "查看完整日报",
"singleURL": "https://kovascape.example.com/daily/2026-07-26.html",
"btnOrientation": "0"
}
}
速率限制
- 每分钟最多 20 条(
errcode: 660026, errmsg: "sending too many messages per minute")
- 批量测试时每次循环 sleep 3-5 秒或退避
- 一次失败不要立即重试,先 sleep 30+ 秒
完整 Python 封装示例
import json
import time
import hmac
import hashlib
import base64
import urllib.parse
from pathlib import Path
import requests
class DingTalkWebhook:
def __init__(self, webhook_url: str, secret: str = "", keyword: str = ""):
self.base_url = webhook_url
self.secret = secret
self.keyword = keyword
def _signed_url(self) -> str:
if not self.secret:
return self.base_url
ts = str(round(time.time() * 1000))
s = f"{ts}\n{self.secret}"
h = hmac.new(self.secret.encode(), s.encode(), hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(h))
return f"{self.base_url}×tamp={ts}&sign={sign}"
def _prepend_keyword(self, payload: dict) -> dict:
"""关键词模式:消息正文里塞关键词
⚠️ Action Card 必须同时在 title 和 text 中都含关键词,
否则 API 返回 errcode:310000 关键词不匹配
"""
.keyword:
payload
mt = payload.get()
mt == :
payload[][] =
mt == :
payload[][] =
mt == :
.keyword payload[].get(, ):
payload[][] =
payload[][] =
payload
() -> :
payload = ._prepend_keyword(payload)
url = ._signed_url()
body = json.dumps(payload, ensure_ascii=).encode()
r = requests.post(url, data=body,
headers={: })
r.json()
常见错误码
| errcode | 含义 | 处理 |
|---|
| 0 | 成功 | — |
| 310000 | 关键词不匹配 | 检查消息正文是否含关键词 |
| 660026 | 频率过高 | sleep 30+ 秒重试 |
| 40001 | 时间戳过期(加签) | 重新生成 timestamp |
| 43004 | 加签错误 | 检查 secret 是否正确 |
调试清单
- ✅ Webhook URL 完整(包括 access_token)
- ✅ 消息正文含关键词(在群设置 → 安全设置查看)
- ✅ 加签模式下,URL 含
timestamp 和 sign 参数
- ✅ 消息类型字段名正确(msgtype / text / markdown / actionCard)
- ✅ 中文字符用文件 payload 或 Python requests(不要 shell 内联)
- ✅ 不在 IP 白名单时考虑切换机器人配置