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 钉钉对比表和 消息卡片模板,可直接用于飞书集成开发项目。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。