
Feishu Channel
- 11 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/agi-super-skills
feishu-channel is a Claude Code skill that builds a two-way Feishu (Lark) message channel connecting a bot's webhook events to an AI assistant.
About
feishu-channel is a Claude Code skill that integrates Feishu (Lark) with OpenClaw as a two-way message channel. A developer uses it to receive Feishu messages through webhook event subscriptions and send text, rich-text, and card messages back via lark-mcp or the Feishu HTTP API. It covers bot app creation, event subscription, webhook deployment, permissions, and message format conversion.
- Two-way Feishu/Lark message channel via bot webhook and lark-mcp
- Handles DMs, group chats, @-mention detection, and card messages
- Documents required Feishu app permissions and event subscriptions
Feishu Channel by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,471 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
feishu-channel capabilities & compatibility
Free; needs a Feishu App ID/Secret and a public webhook URL.
- Capabilities
- orchestration
- Use cases
- orchestration
- Pricing
- Free
What feishu-channel says it does
将飞书接入 OpenClaw,实现双向消息通道。
allowed-tools: mcp__lark-mcp_*, Bash, Read, Write, Edit
npx skills add https://github.com/aaaaqwq/agi-super-skills --skill feishu-channelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/agi-super-skills ↗ |
What it does
Build a two-way Feishu/Lark message channel so an AI assistant receives and sends direct and group messages via a bot and webhook.
Who is it for?
Building an AI chat bot on Feishu that receives and replies to direct and group messages.
Skip if: Feishu platform automation like Bitable or docs; the docs point that to feishu-automation instead.
When should I use this skill?
You want Feishu messages to trigger an AI response or to send messages from OpenClaw to Feishu.
What you get
A working Feishu channel that receives events and sends text, rich-text, and card replies.
- Deployed Feishu webhook service
- Two-way message channel
By the numbers
- Two message directions (receive + send)
- 3 required Feishu permissions
Files
飞书 Channel 集成
将飞书接入 OpenClaw,实现双向消息通道。
与 feishu-automation 的区别
| 特性 | feishu-channel | feishu-automation |
|---|---|---|
| 主要用途 | 消息通道集成 | 平台自动化操作 |
| 消息接收 | ✅ Webhook 事件订阅 | ❌ 不支持 |
| 消息发送 | ✅ 实时对话 | ✅ 通知推送 |
| 多维表格 | ❌ 不涉及 | ✅ 完整支持 |
| 文档管理 | ❌ 不涉及 | ✅ 完整支持 |
| 适用场景 | AI 对话机器人 | 数据同步、自动化工作流 |
架构概述
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ 飞书用户 │ ←→ │ 飞书开放平台 │ ←→ │ OpenClaw │
│ (私聊/群聊) │ │ (Webhook) │ │ Gateway │
└─────────────┘ └──────────────────┘ └─────────────┘
↓
┌──────────────────┐
│ Webhook 服务 │
│ - 事件验证 │
│ - 消息解析 │
│ - 格式转换 │
└──────────────────┘核心组件
1. 飞书机器人应用
在飞书开放平台创建的企业自建应用,负责:
- 接收用户消息(通过事件订阅)
- 发送消息(通过消息 API)
- 管理权限和安全
2. Webhook 服务
接收飞书事件推送,转发给 OpenClaw Gateway。
3. 消息发送 API
通过 lark-mcp 工具或直接调用飞书 API 发送消息。
快速开始
前置条件
- 飞书开放平台账号
- 企业自建应用(机器人能力)
- OpenClaw Gateway 运行中
- 公网可访问的 Webhook URL(或使用内网穿透)
1. 创建飞书应用
1. 访问 飞书开放平台 2. 创建企业自建应用 3. 添加「机器人」能力 4. 配置权限(见下方权限列表) 5. 获取 App ID 和 App Secret
2. 配置事件订阅
1. 在应用管理页面,进入「事件订阅」 2. 配置请求地址:https://your-domain.com/webhook/feishu 3. 订阅事件:
im.message.receive_v1- 接收消息im.message.message_read_v1- 消息已读(可选)
3. 部署 Webhook 服务
cd /home/aa/clawd/skills/feishu-channel
npm install
cp .env.example .env
# 编辑 .env 填入配置
npm start4. 发布应用
1. 在飞书开放平台提交审核 2. 审核通过后发布 3. 在飞书中搜索并添加机器人
配置说明
环境变量 (.env)
# 飞书应用配置 (必需)
FEISHU_APP_ID=cli_xxxxxxxxxx
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
# 事件订阅验证 Token
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxx
# 事件加密 Key (可选,推荐启用)
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxx
# OpenClaw Gateway 配置
OPENCLAW_GATEWAY_URL=http://127.0.0.1:18789
OPENCLAW_WEBHOOK_SECRET=your_webhook_secret
# 安全配置
# 允许的用户 open_id (逗号分隔,留空允许所有)
ALLOWED_USERS=ou_xxx,ou_yyy
# 允许的群聊 chat_id (逗号分隔,留空允许所有)
ALLOWED_GROUPS=oc_xxx,oc_yyy
# 群聊行为
REQUIRE_MENTION_IN_GROUP=true
# 服务端口
PORT=3002
# 日志级别
LOG_LEVEL=info飞书应用权限
| 权限 | 说明 | 必需 |
|---|---|---|
im:message | 获取与发送单聊、群组消息 | ✅ |
im:message.group_at_msg | 接收群聊中@机器人消息 | ✅ |
im:message.p2p_msg | 接收用户发给机器人的单聊消息 | ✅ |
im:chat | 获取群组信息 | 推荐 |
contact:user.base | 获取用户基本信息 | 推荐 |
im:resource | 获取与上传图片或文件资源 | 可选 |
OpenClaw 配置 (openclaw.json)
{
"channels": {
"feishu": {
"enabled": true,
"appId": "cli_xxxxxxxxxx",
"appSecret": "env:FEISHU_APP_SECRET",
"webhookUrl": "https://your-domain.com/webhook/feishu",
"dmPolicy": "allowlist",
"allowFrom": ["ou_xxx", "ou_yyy"],
"groups": {
"oc_xxx": {
"name": "工作群",
"requireMention": true
}
}
}
}
}消息格式
接收消息 (Webhook Event)
飞书原始事件格式:
{
"schema": "2.0",
"header": {
"event_id": "xxx",
"event_type": "im.message.receive_v1",
"create_time": "1706745600000",
"token": "verification_token",
"app_id": "cli_xxx"
},
"event": {
"sender": {
"sender_id": {
"open_id": "ou_xxx",
"user_id": "xxx",
"union_id": "on_xxx"
},
"sender_type": "user"
},
"message": {
"message_id": "om_xxx",
"root_id": "",
"parent_id": "",
"create_time": "1706745600000",
"chat_id": "oc_xxx",
"chat_type": "group",
"message_type": "text",
"content": "{\"text\":\"@_user_1 你好\"}",
"mentions": [
{
"key": "@_user_1",
"id": {
"open_id": "ou_bot"
},
"name": "OpenClaw助手"
}
]
}
}
}转换后的 OpenClaw 格式:
{
"type": "message",
"channel": "feishu",
"messageId": "om_xxx",
"from": {
"id": "ou_xxx",
"name": "张三"
},
"chat": {
"id": "oc_xxx",
"type": "group",
"name": "工作群"
},
"text": "你好",
"mentions": ["ou_bot"],
"isMentioned": true,
"timestamp": 1706745600000
}发送消息
使用 lark-mcp 工具
// 发送文本消息
await mcp__lark-mcp_sendMessage({
receive_id: "ou_xxx", // 或 oc_xxx (群聊)
receive_id_type: "open_id", // 或 chat_id
msg_type: "text",
content: JSON.stringify({
text: "收到,正在处理..."
})
});
// 发送富文本消息
await mcp__lark-mcp_sendMessage({
receive_id: "oc_xxx",
receive_id_type: "chat_id",
msg_type: "post",
content: JSON.stringify({
zh_cn: {
title: "任务完成",
content: [
[
{ tag: "text", text: "已完成 " },
{ tag: "a", text: "查看详情", href: "https://example.com" }
]
]
}
})
});
// 发送卡片消息
await mcp__lark-mcp_sendMessage({
receive_id: "oc_xxx",
receive_id_type: "chat_id",
msg_type: "interactive",
content: JSON.stringify({
config: { wide_screen_mode: true },
header: {
template: "turquoise",
title: { content: "通知", tag: "plain_text" }
},
elements: [
{
tag: "div",
text: { content: "**重要通知**", tag: "lark_md" }
},
{
tag: "action",
actions: [
{
tag: "button",
text: { content: "确认", tag: "plain_text" },
type: "primary"
}
]
}
]
})
});使用 HTTP API
# 发送文本消息
curl -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"receive_id": "ou_xxx",
"msg_type": "text",
"content": "{\"text\":\"Hello!\"}"
}'安全策略
事件验证
飞书事件订阅支持两种验证方式:
1. Verification Token: 简单的 Token 验证 2. Encrypt Key: AES 加密(推荐)
// 验证示例
function verifyEvent(body, token) {
if (body.token !== token) {
throw new Error('Invalid verification token');
}
}
// 解密示例
function decryptEvent(encrypt, key) {
const crypto = require('crypto');
const decipher = crypto.createDecipheriv(
'aes-256-cbc',
crypto.createHash('sha256').update(key).digest(),
Buffer.alloc(16, 0)
);
return JSON.parse(
decipher.update(encrypt, 'base64', 'utf8') + decipher.final('utf8')
);
}私聊策略
| 策略 | 说明 |
|---|---|
open | 允许所有人私聊(危险) |
allowlist | 仅允许 allowFrom 列表中的用户 |
群聊策略
| 配置 | 说明 |
|---|---|
requireMention: true | 必须@机器人才响应 |
allowFrom | 群内允许触发的用户列表 |
使用场景
1. 智能问答机器人
用户: @OpenClaw助手 帮我查一下今天的会议安排
Bot: 今天的会议安排:
- 10:00 产品评审会 (会议室A)
- 14:00 技术分享会 (线上)
- 16:00 周例会 (会议室B)2. 工作流通知
// 当任务完成时发送通知
await mcp__lark-mcp_sendMessage({
receive_id: "oc_work_group",
receive_id_type: "chat_id",
msg_type: "interactive",
content: JSON.stringify({
header: {
template: "green",
title: { content: "✅ 任务完成", tag: "plain_text" }
},
elements: [
{
tag: "div",
text: { content: "数据同步任务已完成\n处理记录: 1,234 条", tag: "lark_md" }
}
]
})
});3. 审批流程
用户: @OpenClaw助手 提交请假申请,明天休息一天
Bot: 已收到请假申请,正在提交审批...
[卡片消息: 请假申请详情 + 审批按钮]故障排查
Webhook 无法接收消息
1. 检查 Webhook URL 是否公网可访问 2. 检查 Verification Token 是否正确 3. 查看飞书开放平台的事件推送日志 4. 确认应用已发布且用户已添加机器人
消息发送失败
1. 检查 App ID 和 App Secret 2. 确认应用权限已开启 3. 检查 access_token 是否过期 4. 查看 API 返回的错误码
权限不足
1. 在飞书开放平台检查应用权限 2. 确认权限已审核通过 3. 重新获取 access_token
限制说明
飞书平台限制
- API 调用频率限制(参考官方文档)
- 消息长度限制
- 文件大小限制
功能限制
- 不支持语音消息
- 卡片消息交互需要额外配置回调
- 部分高级功能需要企业认证
相关文件
scripts/feishu-webhook.js- Webhook 服务代码.env.example- 环境变量模板references/feishu-api.md- 飞书 API 参考references/message-types.md- 消息类型说明
TODO
- [ ] 创建飞书应用并获取凭证
- [ ] 配置事件订阅
- [ ] 部署 Webhook 服务
- [ ] 测试私聊消息收发
- [ ] 测试群聊 @提及
- [ ] 配置安全策略
- [ ] 实现卡片消息交互
{
"name": "openclaw-feishu-webhook",
"version": "1.0.0",
"description": "Feishu Webhook Server for OpenClaw - 飞书消息通道集成",
"main": "scripts/feishu-webhook.js",
"scripts": {
"start": "node scripts/feishu-webhook.js",
"dev": "LOG_LEVEL=verbose node scripts/feishu-webhook.js"
},
"dependencies": {
"express": "^4.18.2",
"axios": "^1.6.0",
"dotenv": "^16.3.1"
},
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"feishu",
"lark",
"openclaw",
"webhook",
"chatbot"
],
"license": "MIT"
}
飞书 API 参考
认证
获取 tenant_access_token
curl -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
-H "Content-Type: application/json" \
-d '{
"app_id": "cli_xxx",
"app_secret": "xxx"
}'响应:
{
"code": 0,
"msg": "success",
"tenant_access_token": "t-xxx",
"expire": 7200
}消息 API
发送消息
POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id
Authorization: Bearer {tenant_access_token}
Content-Type: application/json
{
"receive_id": "ou_xxx",
"msg_type": "text",
"content": "{\"text\":\"Hello!\"}"
}receive_id_type 类型
| 类型 | 说明 |
|---|---|
open_id | 用户 open_id |
user_id | 用户 user_id |
union_id | 用户 union_id |
email | 用户邮箱 |
chat_id | 群聊 chat_id |
回复消息
POST https://open.feishu.cn/open-apis/im/v1/messages/{message_id}/reply
Authorization: Bearer {tenant_access_token}
Content-Type: application/json
{
"msg_type": "text",
"content": "{\"text\":\"收到!\"}"
}获取消息
GET https://open.feishu.cn/open-apis/im/v1/messages/{message_id}
Authorization: Bearer {tenant_access_token}消息类型
文本消息 (text)
{
"msg_type": "text",
"content": "{\"text\":\"Hello World\"}"
}支持 @ 用户:
{
"text": "<at user_id=\"ou_xxx\">张三</at> 你好"
}富文本消息 (post)
{
"msg_type": "post",
"content": "{\"zh_cn\":{\"title\":\"标题\",\"content\":[[{\"tag\":\"text\",\"text\":\"内容\"}]]}}"
}富文本元素:
text- 文本a- 链接at- @用户img- 图片
图片消息 (image)
{
"msg_type": "image",
"content": "{\"image_key\":\"img_xxx\"}"
}需要先上传图片获取 image_key。
卡片消息 (interactive)
{
"msg_type": "interactive",
"content": "{\"config\":{\"wide_screen_mode\":true},\"header\":{\"title\":{\"tag\":\"plain_text\",\"content\":\"标题\"}},\"elements\":[{\"tag\":\"div\",\"text\":{\"tag\":\"plain_text\",\"content\":\"内容\"}}]}"
}卡片消息详解
基本结构
{
"config": {
"wide_screen_mode": true,
"enable_forward": true
},
"header": {
"title": {
"tag": "plain_text",
"content": "卡片标题"
},
"template": "blue"
},
"elements": []
}header.template 颜色
blue- 蓝色wathet- 浅蓝turquoise- 青色green- 绿色yellow- 黄色orange- 橙色red- 红色carmine- 深红violet- 紫色purple- 深紫indigo- 靛蓝grey- 灰色
元素类型
div - 内容块
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**粗体** _斜体_ [链接](https://example.com)"
}
}hr - 分割线
{
"tag": "hr"
}action - 按钮组
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "确认"
},
"type": "primary",
"value": {
"key": "value"
}
}
]
}按钮类型:
default- 默认primary- 主要danger- 危险
note - 备注
{
"tag": "note",
"elements": [
{
"tag": "plain_text",
"content": "备注信息"
}
]
}事件订阅
消息接收事件
事件类型:im.message.receive_v1
{
"schema": "2.0",
"header": {
"event_id": "xxx",
"event_type": "im.message.receive_v1",
"create_time": "1706745600000",
"token": "verification_token",
"app_id": "cli_xxx"
},
"event": {
"sender": {
"sender_id": {
"open_id": "ou_xxx",
"user_id": "xxx",
"union_id": "on_xxx"
},
"sender_type": "user"
},
"message": {
"message_id": "om_xxx",
"root_id": "",
"parent_id": "",
"create_time": "1706745600000",
"chat_id": "oc_xxx",
"chat_type": "group",
"message_type": "text",
"content": "{\"text\":\"@_user_1 你好\"}",
"mentions": [
{
"key": "@_user_1",
"id": {
"open_id": "ou_bot"
},
"name": "机器人"
}
]
}
}
}URL 验证
首次配置事件订阅时,飞书会发送验证请求:
{
"challenge": "xxx",
"token": "verification_token",
"type": "url_verification"
}需要返回:
{
"challenge": "xxx"
}用户 API
获取用户信息
GET https://open.feishu.cn/open-apis/contact/v3/users/{user_id}?user_id_type=open_id
Authorization: Bearer {tenant_access_token}群聊 API
获取群信息
GET https://open.feishu.cn/open-apis/im/v1/chats/{chat_id}
Authorization: Bearer {tenant_access_token}获取群成员
GET https://open.feishu.cn/open-apis/im/v1/chats/{chat_id}/members
Authorization: Bearer {tenant_access_token}文件 API
上传图片
POST https://open.feishu.cn/open-apis/im/v1/images
Authorization: Bearer {tenant_access_token}
Content-Type: multipart/form-data
image_type: message
image: (binary)上传文件
POST https://open.feishu.cn/open-apis/im/v1/files
Authorization: Bearer {tenant_access_token}
Content-Type: multipart/form-data
file_type: stream
file_name: example.pdf
file: (binary)错误码
| 错误码 | 说明 |
|---|---|
| 0 | 成功 |
| 99991400 | 参数错误 |
| 99991401 | 无权限 |
| 99991402 | 频率限制 |
| 99991403 | 资源不存在 |
参考链接
- 飞书开放平台: https://open.feishu.cn/
- API 文档: https://open.feishu.cn/document/
- 消息卡片搭建工具: https://open.feishu.cn/tool/cardbuilder
飞书消息类型说明
支持的消息类型
| 类型 | msg_type | 说明 |
|---|---|---|
| 文本 | text | 纯文本消息 |
| 富文本 | post | 支持格式化的文本 |
| 图片 | image | 单张图片 |
| 文件 | file | 文件附件 |
| 语音 | audio | 语音消息 |
| 视频 | media | 视频消息 |
| 表情 | sticker | 表情包 |
| 卡片 | interactive | 交互式卡片 |
| 分享群名片 | share_chat | 群聊分享 |
| 分享用户名片 | share_user | 用户名片分享 |
文本消息 (text)
基本格式
{
"text": "Hello World"
}@用户
{
"text": "<at user_id=\"ou_xxx\">张三</at> 你好"
}@所有人
{
"text": "<at user_id=\"all\">所有人</at> 请注意"
}富文本消息 (post)
基本结构
{
"zh_cn": {
"title": "标题",
"content": [
[
{ "tag": "text", "text": "第一段" }
],
[
{ "tag": "text", "text": "第二段" }
]
]
}
}支持的标签
text - 文本
{
"tag": "text",
"text": "普通文本",
"un_escape": false
}a - 链接
{
"tag": "a",
"text": "点击查看",
"href": "https://example.com"
}at - @用户
{
"tag": "at",
"user_id": "ou_xxx",
"user_name": "张三"
}img - 图片
{
"tag": "img",
"image_key": "img_xxx",
"width": 300,
"height": 200
}完整示例
{
"zh_cn": {
"title": "项目周报",
"content": [
[
{ "tag": "text", "text": "本周完成情况:" }
],
[
{ "tag": "text", "text": "1. 完成了 " },
{ "tag": "a", "text": "需求文档", "href": "https://example.com/doc" }
],
[
{ "tag": "text", "text": "2. 修复了 5 个 Bug" }
],
[
{ "tag": "text", "text": "请 " },
{ "tag": "at", "user_id": "ou_xxx", "user_name": "张三" },
{ "tag": "text", "text": " 审核" }
]
]
}
}图片消息 (image)
{
"image_key": "img_xxx"
}需要先通过上传接口获取 image_key。
文件消息 (file)
{
"file_key": "file_xxx"
}需要先通过上传接口获取 file_key。
卡片消息 (interactive)
卡片消息是最灵活的消息类型,支持丰富的布局和交互。
基本结构
{
"config": {
"wide_screen_mode": true,
"enable_forward": true
},
"header": {
"title": {
"tag": "plain_text",
"content": "卡片标题"
},
"template": "blue"
},
"elements": []
}常用元素
文本块 (div)
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**粗体** _斜体_ ~~删除线~~"
}
}多列布局 (column_set)
{
"tag": "column_set",
"flex_mode": "none",
"background_style": "default",
"columns": [
{
"tag": "column",
"width": "weighted",
"weight": 1,
"elements": [
{
"tag": "div",
"text": {
"tag": "plain_text",
"content": "左列"
}
}
]
},
{
"tag": "column",
"width": "weighted",
"weight": 1,
"elements": [
{
"tag": "div",
"text": {
"tag": "plain_text",
"content": "右列"
}
}
]
}
]
}按钮 (button)
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "确认"
},
"type": "primary",
"value": {
"action": "confirm"
}
},
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "取消"
},
"type": "default"
}
]
}图片 (img)
{
"tag": "img",
"img_key": "img_xxx",
"alt": {
"tag": "plain_text",
"content": "图片描述"
}
}完整卡片示例
{
"config": {
"wide_screen_mode": true
},
"header": {
"title": {
"tag": "plain_text",
"content": "🎉 任务完成通知"
},
"template": "green"
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**任务名称**: 数据同步\n**完成时间**: 2024-02-01 10:30\n**处理记录**: 1,234 条"
}
},
{
"tag": "hr"
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "查看详情"
},
"type": "primary",
"url": "https://example.com/task/123"
}
]
},
{
"tag": "note",
"elements": [
{
"tag": "plain_text",
"content": "由 OpenClaw 自动发送"
}
]
}
]
}Markdown 语法 (lark_md)
在卡片消息中使用 lark_md 标签时,支持以下 Markdown 语法:
| 语法 | 效果 |
|---|---|
**text** | 粗体 |
_text_ | _斜体_ |
~~text~~ | ~~删除线~~ |
[text](url) | 链接 |
<at id=xxx></at> | @用户 |
消息接收解析
文本消息
{
"message_type": "text",
"content": "{\"text\":\"Hello\"}"
}解析:
const content = JSON.parse(message.content);
const text = content.text; // "Hello"带 @提及的消息
{
"message_type": "text",
"content": "{\"text\":\"@_user_1 你好\"}",
"mentions": [
{
"key": "@_user_1",
"id": {
"open_id": "ou_xxx"
},
"name": "机器人"
}
]
}解析:
const content = JSON.parse(message.content);
let text = content.text; // "@_user_1 你好"
// 移除 @提及占位符
for (const mention of message.mentions) {
text = text.replace(mention.key, '').trim();
}
// text = "你好"参考链接
- 消息类型文档: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/im-v1/message/create_json
- 卡片搭建工具: https://open.feishu.cn/tool/cardbuilder
#!/usr/bin/env node
/**
* Feishu Webhook Server for OpenClaw
*
* 接收飞书事件订阅,转发消息到 OpenClaw Gateway
*/
require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
// ============ 配置 ============
const config = {
// 飞书应用配置
appId: process.env.FEISHU_APP_ID || 'YOUR_APP_ID',
appSecret: process.env.FEISHU_APP_SECRET || 'YOUR_APP_SECRET',
verificationToken: process.env.FEISHU_VERIFICATION_TOKEN || '',
encryptKey: process.env.FEISHU_ENCRYPT_KEY || '',
// OpenClaw Gateway
openclawGatewayUrl: process.env.OPENCLAW_GATEWAY_URL || 'http://127.0.0.1:18789',
openclawWebhookSecret: process.env.OPENCLAW_WEBHOOK_SECRET || '',
// 安全配置
allowedUsers: process.env.ALLOWED_USERS?.split(',').filter(Boolean) || [],
allowedGroups: process.env.ALLOWED_GROUPS?.split(',').filter(Boolean) || [],
requireMentionInGroup: process.env.REQUIRE_MENTION_IN_GROUP !== 'false',
// 服务端口
port: parseInt(process.env.PORT || '3002'),
// 日志级别
logLevel: process.env.LOG_LEVEL || 'info',
};
// ============ Access Token 管理 ============
let accessToken = null;
let tokenExpireTime = 0;
async function getAccessToken() {
if (accessToken && Date.now() < tokenExpireTime - 60000) {
return accessToken;
}
try {
const response = await axios.post(
'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal',
{
app_id: config.appId,
app_secret: config.appSecret,
}
);
if (response.data.code === 0) {
accessToken = response.data.tenant_access_token;
tokenExpireTime = Date.now() + response.data.expire * 1000;
console.log('Access token 已更新');
return accessToken;
} else {
throw new Error(`获取 token 失败: ${response.data.msg}`);
}
} catch (error) {
console.error('获取 access token 失败:', error.message);
throw error;
}
}
// ============ 事件解密 ============
function decryptEvent(encrypt) {
if (!config.encryptKey) {
throw new Error('未配置 FEISHU_ENCRYPT_KEY');
}
const key = crypto.createHash('sha256').update(config.encryptKey).digest();
const encryptBuffer = Buffer.from(encrypt, 'base64');
const iv = encryptBuffer.slice(0, 16);
const encrypted = encryptBuffer.slice(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, undefined, 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
// ============ 用户信息获取 ============
const userCache = new Map();
async function getUserInfo(openId) {
if (userCache.has(openId)) {
return userCache.get(openId);
}
try {
const token = await getAccessToken();
const response = await axios.get(
`https://open.feishu.cn/open-apis/contact/v3/users/${openId}`,
{
headers: { Authorization: `Bearer ${token}` },
params: { user_id_type: 'open_id' },
}
);
if (response.data.code === 0) {
const user = response.data.data.user;
const info = {
id: openId,
name: user.name,
email: user.email,
avatar: user.avatar?.avatar_origin,
};
userCache.set(openId, info);
return info;
}
} catch (error) {
console.error('获取用户信息失败:', error.message);
}
return { id: openId, name: 'Unknown' };
}
// ============ 群聊信息获取 ============
const chatCache = new Map();
async function getChatInfo(chatId) {
if (chatCache.has(chatId)) {
return chatCache.get(chatId);
}
try {
const token = await getAccessToken();
const response = await axios.get(
`https://open.feishu.cn/open-apis/im/v1/chats/${chatId}`,
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (response.data.code === 0) {
const chat = response.data.data;
const info = {
id: chatId,
name: chat.name,
type: chat.chat_mode === 'p2p' ? 'private' : 'group',
};
chatCache.set(chatId, info);
return info;
}
} catch (error) {
console.error('获取群聊信息失败:', error.message);
}
return { id: chatId, name: 'Unknown', type: 'group' };
}
// ============ 消息处理 ============
async function handleMessageEvent(event) {
const { sender, message } = event;
// 只处理用户消息
if (sender.sender_type !== 'user') {
console.log('忽略非用户消息');
return;
}
const senderId = sender.sender_id.open_id;
const chatId = message.chat_id;
const chatType = message.chat_type;
// 权限检查
if (!checkPermission(senderId, chatId, chatType)) {
console.log('权限检查未通过,忽略消息');
return;
}
// 群聊 @提及检查
const mentions = message.mentions || [];
const isMentioned = mentions.some(m => m.id?.open_id === config.appId);
if (chatType === 'group' && config.requireMentionInGroup && !isMentioned) {
console.log('群消息未@机器人,忽略');
return;
}
// 解析消息内容
let text = '';
if (message.message_type === 'text') {
const content = JSON.parse(message.content);
text = content.text || '';
// 移除 @提及文本
for (const mention of mentions) {
text = text.replace(mention.key, '').trim();
}
} else {
console.log(`暂不支持的消息类型: ${message.message_type}`);
return;
}
// 获取用户和群聊信息
const userInfo = await getUserInfo(senderId);
const chatInfo = chatType === 'group'
? await getChatInfo(chatId)
: { id: senderId, type: 'private' };
// 构建 OpenClaw 消息格式
const payload = {
type: 'message',
channel: 'feishu',
messageId: message.message_id,
timestamp: parseInt(message.create_time),
from: userInfo,
chat: chatInfo,
text: text,
mentions: mentions.map(m => m.id?.open_id).filter(Boolean),
isMentioned: isMentioned,
raw: event,
};
// 转发到 OpenClaw
await forwardToOpenClaw(payload);
}
function checkPermission(senderId, chatId, chatType) {
if (chatType === 'p2p') {
if (config.allowedUsers.length === 0) return true;
return config.allowedUsers.includes(senderId);
}
if (chatType === 'group') {
if (config.allowedGroups.length === 0) return true;
return config.allowedGroups.includes(chatId);
}
return false;
}
async function forwardToOpenClaw(payload) {
try {
const headers = {
'Content-Type': 'application/json',
};
if (config.openclawWebhookSecret) {
headers['Authorization'] = `Bearer ${config.openclawWebhookSecret}`;
}
const response = await axios.post(
`${config.openclawGatewayUrl}/webhook/feishu`,
payload,
{ headers, timeout: 30000 }
);
console.log('消息已转发到 OpenClaw:', response.status);
} catch (error) {
console.error('转发消息到 OpenClaw 失败:', error.message);
}
}
// ============ Express 服务 ============
const app = express();
app.use(express.json());
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'ok',
appId: config.appId,
hasToken: !!accessToken,
});
});
// 飞书 Webhook 端点
app.post('/webhook/feishu', async (req, res) => {
try {
let body = req.body;
// 处理加密事件
if (body.encrypt) {
body = decryptEvent(body.encrypt);
}
// URL 验证 (首次配置时飞书会发送验证请求)
if (body.type === 'url_verification') {
console.log('收到 URL 验证请求');
return res.json({ challenge: body.challenge });
}
// 验证 token
if (config.verificationToken && body.token !== config.verificationToken) {
console.error('验证 token 不匹配');
return res.status(401).json({ error: 'Invalid token' });
}
// 处理事件回调
if (body.header?.event_type === 'im.message.receive_v1') {
console.log('收到消息事件');
// 立即返回 200,避免飞书重试
res.json({ code: 0 });
// 异步处理消息
handleMessageEvent(body.event).catch(console.error);
return;
}
// 其他事件
console.log('收到其他事件:', body.header?.event_type || body.type);
res.json({ code: 0 });
} catch (error) {
console.error('处理 Webhook 失败:', error);
res.status(500).json({ error: error.message });
}
});
// 发送消息 API (供 OpenClaw 调用)
app.post('/api/send', async (req, res) => {
try {
// 验证 secret
const authHeader = req.headers.authorization;
if (config.openclawWebhookSecret) {
if (authHeader !== `Bearer ${config.openclawWebhookSecret}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
}
const { to, type, content, receiveIdType } = req.body;
if (!to || !content) {
return res.status(400).json({ error: 'Missing required fields' });
}
const token = await getAccessToken();
const response = await axios.post(
`https://open.feishu.cn/open-apis/im/v1/messages`,
{
receive_id: to,
msg_type: type || 'text',
content: typeof content === 'string' ? content : JSON.stringify(content),
},
{
headers: { Authorization: `Bearer ${token}` },
params: { receive_id_type: receiveIdType || 'open_id' },
}
);
if (response.data.code === 0) {
res.json({ success: true, data: response.data.data });
} else {
res.status(400).json({ error: response.data.msg, code: response.data.code });
}
} catch (error) {
console.error('发送消息失败:', error.message);
res.status(500).json({ error: error.message });
}
});
// ============ 启动 ============
async function main() {
console.log('\n========================================');
console.log('🐦 Feishu Webhook Server for OpenClaw');
console.log('========================================\n');
// 检查配置
if (config.appId === 'YOUR_APP_ID') {
console.error('❌ 请配置 FEISHU_APP_ID 环境变量');
process.exit(1);
}
if (config.appSecret === 'YOUR_APP_SECRET') {
console.error('❌ 请配置 FEISHU_APP_SECRET 环境变量');
process.exit(1);
}
// 预获取 access token
try {
await getAccessToken();
console.log('✅ Access token 获取成功');
} catch (error) {
console.error('⚠️ 无法获取 access token,请检查配置');
}
// 启动服务
app.listen(config.port, () => {
console.log(`\n📡 服务已启动: http://localhost:${config.port}`);
console.log(` - 健康检查: GET /health`);
console.log(` - Webhook: POST /webhook/feishu`);
console.log(` - 发送消息: POST /api/send`);
console.log(`\n配置飞书事件订阅 URL: https://your-domain.com/webhook/feishu\n`);
});
}
main().catch(console.error);
Feishu Channel TODO
必需配置项
1. 飞书应用凭证
- App ID: 在飞书开放平台创建应用后获取
- App Secret: 应用密钥
- Verification Token: 事件订阅验证 Token
- Encrypt Key: 事件加密密钥(推荐)
2. 应用权限
需要在飞书开放平台开启以下权限:
im:message- 消息读写im:message.group_at_msg- 群聊@消息im:message.p2p_msg- 私聊消息im:chat- 群聊信息contact:user.base- 用户基本信息
3. 事件订阅
- 配置 Webhook URL(需要公网可访问)
- 订阅
im.message.receive_v1事件
待解决问题
架构问题
1. OpenClaw Channel 插件开发
- 当前方案使用独立 Webhook 服务桥接
- 可考虑开发原生 OpenClaw 插件(参考 telegram 插件)
- 需要研究 OpenClaw 插件 SDK
2. 消息路由
- Webhook 服务如何与 OpenClaw Gateway 通信
- 会话上下文如何保持
3. 卡片消息交互
- 按钮点击回调需要额外配置
- 需要实现卡片交互处理逻辑
技术问题
1. Webhook URL
- 需要公网可访问的 URL
- 可使用 ngrok/frp 等内网穿透工具
- 或部署到云服务器
2. Access Token 管理
- Token 有效期 2 小时
- 需要自动刷新机制
3. 消息可靠性
- 飞书事件推送有重试机制
- 需要实现幂等处理
安全问题
1. 事件验证
- 必须验证 Verification Token
- 推荐启用 Encrypt Key 加密
2. 权限控制
- 私聊白名单
- 群聊白名单
后续优化方向
功能增强
- [ ] 支持图片消息
- [ ] 支持文件消息
- [ ] 支持卡片消息交互
- [ ] 支持消息撤回
- [ ] 支持消息已读回执
稳定性
- [ ] Token 自动刷新
- [ ] 事件去重
- [ ] 错误重试
- [ ] 日志监控
部署
- [ ] Docker 容器化
- [ ] 系统服务配置
- [ ] 健康检查
- [ ] 自动重启
与 feishu-automation 的集成
feishu-channel 专注于消息通道,可以与 feishu-automation 配合使用:
1. 消息触发自动化
- 用户发送消息 → feishu-channel 接收
- AI 处理后调用 feishu-automation 操作多维表格
2. 自动化结果通知
- feishu-automation 完成任务
- 通过 feishu-channel 发送通知
替代方案
如果不想自建 Webhook 服务:
1. 使用 lark-mcp 的消息功能
- 仅支持发送,不支持接收
- 适合单向通知场景
2. 飞书机器人 Webhook
- 仅支持发送到群聊
- 不需要创建应用
参考资源
- 飞书开放平台: https://open.feishu.cn/
- 事件订阅文档: https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM
- 消息 API 文档: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/im-v1/message/create
- 卡片搭建工具: https://open.feishu.cn/tool/cardbuilder
Related skills
FAQ
How is it different from feishu-automation?
feishu-channel focuses on two-way message channel integration; feishu-automation focuses on platform automation like Bitable and documents.
What Feishu permissions are required?
im:message, im:message.group_at_msg, and im:message.p2p_msg are required, with im:chat and contact:user.base recommended.