返回403是因为未加签或加签错误;需用Secret对“timestamp Secret”做HMAC-SHA256再base64编码,并确保JSON中markdown字段结构正确、换行符为\n。

直接用 http.Post 发送 JSON 就能推,但不加签会 403;加签逻辑必须自己算,官方没提供 Go 标准库函数。
为什么发请求返回 403 forbidden
钉钉自定义机器人开启「加签」后,webhook URL 里不再只有 access_token,还必须带 timestamp 和 sign 参数,否则服务端直接拒绝。
-
timestamp是当前毫秒时间戳(time.Now().UnixMilli()) -
sign是用Secret对timestamp + " " + Secret做HMAC-SHA256,再base64编码 - 最终 URL 形如:
https://oapi.dingtalk.com/robot/send?access_token=xxx×tamp=1712345678901&sign=xxx - 如果只传
access_token,哪怕 token 正确,也一定 403
如何构造合法的 sign 参数
Go 没有内置加签工具函数,得手动拼、哈希、编码。别漏掉换行符
—— 这是钉钉文档里最容易被忽略的细节。
- 先拼原始字符串:
tsStr := strconv.FormatInt(ts, 10) + " " + secret - 再 HMAC:
h := hmac.New(sha256.New, []byte(secret)),然后h.Write([]byte(tsStr)) - 最后 base64:
sign := base64.StdEncoding.EncodeToString(h.Sum(nil)) - 注意:
secret是创建机器人时生成的完整字符串(以SEC开头),不是部分截取
推送 markdown 消息时 text 字段要嵌套两层
钉钉 API 要求 markdown 类型消息的结构固定,text 字段不是直接放内容,而是作为 markdown.text 的子字段存在。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
{"msgtype":"markdown","text":"# hello"}→ 会静默失败或报错invalid msgtype - 正确结构:
{"msgtype":"markdown","markdown":{"title":"标题","text":"# hello\n> 引用"}} - 模板里换行要用
\n(JSON 字符串内),不是;实际渲染时钉钉会识别 Markdown 换行 - 如果用
text/template渲染,记得调用template.HTMLEscapeString防止特殊字符破坏 JSON
如何安全复用 client 并避免连接泄漏
高频推送场景下,每次新建 http.Client 或不关响应体,会导致文件描述符耗尽、连接超时。
- 全局复用一个
&http.Client{Timeout: 10 * time.Second},不要每次new(http.Client) -
resp.Body必须defer resp.Body.Close(),即使你只读状态码 - 建议用
bytes.NewReader(jsonBytes)代替strings.NewReader,更符合二进制语义 - 若需代理或 TLS 自定义,通过
http.Transport设置,而非反复 new client
加签逻辑和 JSON 结构这两处,错一个字符就失败,调试时建议先用 curl 手动构造一次请求验证签名是否正确,再移植到 Go 代码里。

















