TL;DR:飞书(Lark)Webhook 机制与钉钉类似,但更清晰统一——自定义机器人(群推送 + 回调)、应用事件订阅(企业级通知)。核心差异在于飞书使用 Encrypt Key 对回调 Payload 进行 AES-256-CBC 加密,且消息卡片(Card)生态更强大。
1. 飞书 Webhook 类型速览
| 类型 | 方向 | 触发场景 | 验证方式 | 适用 |
|---|---|---|---|---|
| 自定义机器人 | 你 → 群 / 群 → 你 | 推送消息 / 用户@机器人 | 无 / Encrypt Key + sign | 通知、交互 |
| 应用事件订阅 | 飞书 → 你 | 审批/日程/通讯录变更 | Encrypt Key + Verification Token | 企业数据同步 |
2. 自定义机器人(群推送)
2.1 配置步骤
- 进入飞书群 → 群设置 → 群机器人 → 添加机器人 → 自定义机器人
- 设置名称、描述、头像
- 获得 Webhook URL:
https://open.feishu.cn/open-apis/bot/v2/hook/xxx
- 可选启用签名校验(否则任何人知道 URL 就能推送)
2.2 安全设置(推荐启用)
- 签名校验:使用
secret生成 HMAC-SHA256 + timestamp - IP 白名单:仅指定 IP 段可调
- 关键词:消息中必须包含指定关键词(与钉钉相同)
2.3 Go 推送代码
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
)
type FeishuRobot struct {
WebhookURL string
Secret string // 签名校验密钥
}
func (r *FeishuRobot) SendText(text string) error {
timestamp := time.Now().Unix()
sign := r.sign(timestamp)
payload := map[string]interface{}{
"timestamp": timestamp,
"sign": sign,
"msg_type": "text",
"content": map[string]string{
"text": text,
},
}
body, _ := json.Marshal(payload)
resp, err := http.Post(r.WebhookURL, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
json.NewDecoder(resp.Body).Decode(&result)
if result.Code != 0 {
return fmt.Errorf("feishu robot send failed: %s", result.Msg)
}
return nil
}
func (r *FeishuRobot) sign(timestamp int64) string {
// 签名字串:timestamp + "\n" + secret
strToSign := fmt.Sprintf("%d\n%s", timestamp, r.Secret)
mac := hmac.New(sha256.New, []byte(r.Secret))
mac.Write([]byte(strToSign))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// 使用示例
func main() {
robot := &FeishuRobot{
WebhookURL: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx",
Secret: "xxx",
}
robot.SendText("📢 系统告警:API 响应时间 P99 > 2s")
}
2.4 飞书消息卡片(Card)
飞书的卡片消息比钉钉更强大,支持丰富的交互组件:
func (r *FeishuRobot) SendCard(title, content string) error {
payload := map[string]interface{}{
"msg_type": "interactive",
"card": map[string]interface{}{
"header": map[string]interface{}{
"title": map[string]interface{}{
"tag": "plain_text",
"content": title,
},
"template": "red", // red / orange / green / blue
},
"elements": []map[string]interface{}{
{
"tag": "div",
"text": map[string]interface{}{
"tag": "lark_md",
"content": content,
},
},
{
"tag": "action",
"actions": []map[string]interface{}{
{
"tag": "button",
"text": map[string]interface{}{
"tag": "plain_text",
"content": "查看详情",
},
"type": "primary",
"url": "https://admin.example.com/alerts/123",
},
},
},
},
},
}
body, _ := json.Marshal(payload)
resp, _ := http.Post(r.WebhookURL, "application/json", bytes.NewReader(body))
resp.Body.Close()
return nil
}
卡片效果预览:
┌─────────────────────────────┐
│ 🔴 系统告警 │
├─────────────────────────────┤
│ API 响应时间 P99 > 2s │
│ 影响接口: /api/v1/orders │
│ 发生时间: 10:30:00 │
├─────────────────────────────┤
│ [查看详情] │
└─────────────────────────────┘
3. 自定义机器人回调(用户@机器人)
3.1 配置回调 URL
- 飞书开发者后台 → 机器人 → 事件订阅
- 配置 请求网址:
https://yourapp.com/webhooks/feishu - 订阅事件:
im.message.receive_v1
3.2 回调 Payload 结构
{
"schema": "2.0",
"header": {
"event_id": "xxx",
"event_type": "im.message.receive_v1",
"create_time": "1234567890000",
"token": "xxx",
"app_id": "cli_xxx",
"tenant_key": "xxx"
},
"event": {
"message": {
"message_id": "om_xxx",
"message_type": "text",
"content": "{\"text\":\"@机器人 查询用户\"}",
"chat_id": "oc_xxx",
"sender": {"sender_id": {"user_id": "ou_xxx"}, "sender_type": "user"}
}
}
}
3.3 Go Handler
func handleFeishuWebhook(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var event struct {
Header struct {
EventType string `json:"event_type"`
Token string `json:"token"`
} `json:"header"`
Event struct {
Message struct {
MessageType string `json:"message_type"`
Content string `json:"content"` // JSON 字符串,需二次解析
ChatID string `json:"chat_id"`
} `json:"message"`
} `json:"event"`
}
json.Unmarshal(body, &event)
// 验证 Token
if event.Header.Token != os.Getenv("FEISHU_VERIFICATION_TOKEN") {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
switch event.Header.EventType {
case "im.message.receive_v1":
var content struct {
Text string `json:"text"`
}
json.Unmarshal([]byte(event.Event.Message.Content), &content)
reply := processFeishuCommand(content.Text)
sendFeishuReply(event.Event.Message.ChatID, reply)
}
w.WriteHeader(http.StatusOK)
}
func processFeishuCommand(text string) string {
switch {
case strings.Contains(text, "用户"):
return "👥 今日新增用户 42 人,日活 3,024"
case strings.Contains(text, "订单"):
return "📦 今日订单 128 笔,金额 ¥12,480"
default:
return "🤖 可用指令:查询用户 / 查询订单 / 系统状态"
}
}
func sendFeishuReply(chatID, text string) {
token := getFeishuTenantToken()
url := "https://open.feishu.cn/open-apis/im/v1/messages"
payload := map[string]interface{}{
"receive_id": chatID,
"msg_type": "text",
"content": fmt.Sprintf(`{"text":"%s"}`, text),
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
4. 应用事件订阅(Encrypt Key 解密)
4.1 加密机制
飞书使用 AES-256-CBC 加密回调数据,比钉钉多了一个消息完整性校验步骤:
加密流程:明文 → AES-256-CBC 加密 → Base64 → 放入 encrypt 字段
解密流程:encrypt → Base64 解码 → AES-256-CBC 解密 → 去掉 16 字节随机串 → 明文
4.2 Go 解密实现
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
)
func decryptFeishuEvent(encrypt string, encryptKey string) ([]byte, error) {
// encryptKey 是 32 字节的 Base64 编码字符串 → 解码后 48 字节
key, err := base64.StdEncoding.DecodeString(encryptKey)
if err != nil {
return nil, err
}
// 飞书要求 key 为 32 字节
aesKey := key[:32]
data, err := base64.StdEncoding.DecodeString(encrypt)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
iv := aesKey[:16]
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(data, data)
// PKCS#7 去填充
padLen := int(data[len(data)-1])
return data[16 : len(data)-padLen], nil // 去掉 16 字节随机串
}
4.3 事件处理 Handler
func handleFeishuEvent(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var event struct {
Encrypt string `json:"encrypt"`
Token string `json:"token"` // 挑战验证时携带
Challenge string `json:"challenge"` // 初始配置 URL 验证
}
json.Unmarshal(body, &event)
// ① URL 验证(首次配置回调地址时)
if event.Challenge != "" {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"challenge": event.Challenge,
})
return
}
// ② 解密事件
decrypted, err := decryptFeishuEvent(event.Encrypt, os.Getenv("FEISHU_ENCRYPT_KEY"))
if err != nil {
http.Error(w, "decrypt failed", http.StatusBadRequest)
return
}
var payload struct {
Header struct {
EventType string `json:"event_type"`
} `json:"header"`
Event struct {
ApprovalInstance struct {
InstanceCode string `json:"instance_code"`
Status string `json:"status"` // PENDING / COMPLETED
Result string `json:"result"` // agree / refuse
} `json:"approval_instance"`
} `json:"event"`
}
json.Unmarshal(decrypted, &payload)
switch payload.Header.EventType {
case "approval_instance_status_changed":
handleApprovalChange(payload.Event.ApprovalInstance)
case "contact.user.deleted_v3":
handleUserDeleted(payload)
}
w.WriteHeader(http.StatusOK)
}
5. 飞书 vs 钉钉对比
| 维度 | 飞书 | 钉钉 |
|---|---|---|
| 推送 URL 格式 | open.feishu.cn/open-apis/bot/v2/hook/xxx | oapi.dingtalk.com/robot/send?access_token=xxx |
| 加签格式 | timestamp(秒) + “\n” + secret | timestamp(毫秒) + “\n” + secret |
| 加密算法 | AES-256-CBC(易实现) | AES-CBC(类似) |
| 消息卡片 | 更强大(富交互组件) | 较简单(支持 Markdown) |
| 机器人回复 | 需 tenant_access_token + chat_id | sessionWebhook 直接回复 |
| 开放平台 API | OpenAPI 3.0 风格 | OAPI 传统风格 |
| 日期时间格式 | Unix 秒 | Unix 毫秒(注意区别!) |
⚠️ 最大坑点:飞书回调的时间戳是秒级,钉钉是毫秒级,转换时容易搞混。
6. 常见问题排查
| # | 问题 | 排查 | 修复 |
|---|---|---|---|
| 1 | “msg: key not exist” | Webhook URL 中 hook 后面的 ID 错误 | 确认机器人配置中的 Webhook URL |
| 2 | 推送成功但群里看不到 | 机器人不在群内 / 被禁言 | 重新邀请机器人到群 |
| 3 | 回调 URL 验证失败 | 未正确返回 challenge | Challenge 验证必须原样返回 |
| 4 | Decrypt 失败 | Encrypt Key 不是 32 字节 | 使用 Base64 编码后的字符串,保持 43 字符 |
| 5 | 获取 tenant_access_token 失败 | AppID / AppSecret 错误 | 检查开发者后台的凭证 |
| 6 | 时间戳超过 1h | 系统时间不准 | 配置 NTP,timedatectl set-ntp true |
7. 下一步
- 📖 钉钉 Webhook 集成实战 → — HMAC 加签、群机器人、事件订阅
- 📖 Slack Webhook 集成实战 → — Events API、Block Kit、Signing Secret
- 📖 Webhook 安全最佳实践 → — HMAC/AES 签名与防重放
- 📖 Webhook Gateway 设计 → — 统一接入多平台 Webhook
本文全场约 3,500 词,提供 群机器人推送、自定义机器人回调、应用事件订阅解密的完整 Go 代码,以及 飞书 vs 钉钉对比表和 消息卡片模板,可直接用于飞书集成开发项目。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。
「saas」更多文章
短链接对 SEO 的影响与优化最佳实践
深度解析短链接对 SEO 的影响,覆盖 HTTP 重定向状态码对 PageRank 的传递差异、品牌短链与公共短链的 SEO 对比、Google 索引机制与实战优化建议,帮助 SEO 从业者和营销人员正确使用短链接。
UTM 参数 + 短链接:追踪每一条营销链路
本文系统讲解 UTM 参数的定义、5 个核心字段详解、命名规范,以及 UTM 与短链接结合的最佳实践。涵盖主流 UTM builder 工具对比、数据分析方法、常见错误规避和高级玩法,帮你建立一套完整的营销追踪工作流。
私域流量运营中的短链接策略:从引流到转化
深度解析短链接在微信、抖音、小红书等私域运营场景中的实战策略,涵盖渠道追踪、裂变增长、防封域名、活码技术、转化漏斗优化等核心方法论,帮助 SaaS 企业和品牌商家从引流到转化构建完整的私域增长闭环。