Larksuite (Feishu)
Feishu (国内版) and Lark Suite (海外版) share the same OpenAPI surface. The Ongrid provider treats them as one integration; pick the base URL that matches your tenant.
| Mode | What it does |
|---|---|
| Notification | Alert push to a Feishu/Lark group via custom bot webhook. |
| IM bridge | Two-way agent chat using the WebSocket long-connection. |
Notification mode (custom bot)
The Feishu sender posts a Feishu / Lark custom bot payload to the webhook URL your bot administrator provides. Payload shape:
{
"msg_type": "text",
"content": {"text": "[CRITICAL] swap_high node-01\nswap_in_pages > 1000 for 5m\nsource: alert\ndedupe: alert:swap_high:device=7"},
"timestamp": "1717012345",
"sign": "<base64-hmac>"
}Signing — sign field
When you turn on 签名校验 (signature verification) on the Feishu custom bot, Feishu hands you a shared secret. The sender computes the sign field as:
stringToSign = timestamp + "\n" + secret
sign = base64(HMAC-SHA256(key=stringToSign, message=<empty>))Yes — the secret takes the role of both key material for HMAC and part of the string-to-sign. This is the algorithm Feishu documents, and it's what signFeishu implements.
If you leave the Secret field blank, Ongrid posts without a sign / timestamp — usable only when your bot has signature verification turned off (or has IP allowlisting instead).
Setup
- In the Feishu group → 设置 → 群机器人 → 添加机器人 → 自定义机器人. Give it a name and avatar.
- Tick 签名校验; copy the secret.
- Copy the webhook URL — looks like
https://open.feishu.cn/open-apis/bot/v2/hook/<uuid>(or…/open.larksuite.com/…for Lark Suite). - In Ongrid: Settings → Channels → New → Provider =
feishu→ Endpoint = the webhook URL → Secret = the signing secret.
Custom bot ≠ App
The notification mode uses a custom bot (group-scoped, webhook only). The IM bridge mode uses a Feishu app (tenant-scoped, OAuth
- events). They are different concepts; the credentials don't overlap.
IM bridge mode (long-connection stream)
The two-way bridge uses the Feishu long-connection delivered by the official github.com/larksuite/oapi-sdk-go/v3/ws client. The manager dials Feishu's event endpoint and Feishu pushes events over the WebSocket — no public webhook URL required.
Why stream and not webhook
The webhook mode (mode=webhook) is supported in the schema for back-compat, but the long-connection stream is the recommended path:
- No public ingress needed on the manager.
- Reuses the supervisor's reconnect-with-backoff (the SDK has its own reconnect; the supervisor adds an outer loop for terminal failures).
- The SDK handles signature verification + AES decryption internally, so you don't need to fill
encrypt_keyfor the stream variant — it's webhook-only.
Credential mapping
im_apps column | Feishu meaning |
|---|---|
provider | "feishu" |
mode | "stream" (recommended) or "webhook" |
app_id | Feishu app_id (cli_…). |
app_secret | Feishu app_secret. |
verify_token | Optional. Used by webhook-mode signature verification. |
encrypt_key | Required in webhook mode, ignored in stream mode. |
allow_from | Optional. Feishu is tenant-gated so the allowlist is non-mandatory. |
default_locale
Set to zh for a Feishu tenant whose operators write Chinese, to en for an English Lark team. Empty (default) means the LLM mirrors the user.
Setup
- 开发者后台 → 创建企业自建应用. Get
app_id+app_secret. - 应用功能 → 机器人 → enable. Add the bot to your test chat.
- 权限管理 → grant:
im:message— read messages addressed to the bot.im:message.group_at_msg— group-mention events.im:message.p2p_msg— DM events.im:message:send_as_bot—SendText/EditText.
- 事件订阅 → 长连接模式 (long-connection) → enable.
- In Ongrid: Settings → IM bridge → New → Provider =
feishu→ Mode =stream→ pasteapp_id+app_secret. Save and Enable. @the bot in the chat. The agent picks it up.
tenant_access_token caching
Outbound calls (SendText, EditText) authenticate with a tenant_access_token, refreshed proactively when within 200s of expiry. The token is cached per-Client instance, protected by a sync.Mutex; rotating credentials means rebuilding the client. See tenantAccessToken.
msg_type is required on edits too
Feishu's PUT /open-apis/im/v1/messages/<id> requires msg_type in the body — omitting it returns code: 99992402 (field validation failed) even though the docs page for "edit message" doesn't make that obvious. The provider always sends msg_type: text.
Webhook mode (legacy, kept for compatibility)
Webhook mode is still selectable in the UI; the supervisor verifies signatures and decrypts payloads itself.
Signature verification
The HTTP handler reads X-Lark-Signature and runs VerifyEventSignature:
sig = sha256(timestamp + nonce + encrypt_key + body), hex-encodedYes — SHA-256, not HMAC. The encrypt_key plays the shared-secret role inside the hash input. The verifier never panics on bad input; mismatch returns ErrBadSignature.
Payload decryption
When encrypt_key is set, Feishu wraps the event JSON in AES-256-CBC. The decryption uses:
- Key:
SHA-256(encrypt_key). - IV: the first 16 bytes of the base64-decoded ciphertext.
- Padding: PKCS#7.
See DecryptEvent.
Pick stream mode if you can
Webhook mode requires a public HTTPS endpoint and the encrypt_key plumbing above. Long-connection stream avoids both. Reach for webhook mode only when you specifically need it (e.g. integrating Feishu events with an existing public webhook router).
Quirks
- The Feishu stream client doesn't enforce
allow_fromtoday — the Feishu platform itself is tenant-gated, so only enterprise members reach the bot. If your tenant has guests / outside collaborators, fillallow_fromwith theiropen_idvalues to lock the conversation down further. - Stickers, files, cards, and rich messages are dropped (only
msg_type == "text"triggers the agent). Same S1 contract as Telegram / Slack. - The
RootIdfield is captured asImThread.ImThreadIDso replies inside a thread continue the same session — no need for the user to reset context per question.