Skip to content

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.

ModeWhat it does
NotificationAlert push to a Feishu/Lark group via custom bot webhook.
IM bridgeTwo-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:

json
{
  "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:

text
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

  1. In the Feishu group → 设置 → 群机器人 → 添加机器人 → 自定义机器人. Give it a name and avatar.
  2. Tick 签名校验; copy the secret.
  3. Copy the webhook URL — looks like https://open.feishu.cn/open-apis/bot/v2/hook/<uuid> (or …/open.larksuite.com/… for Lark Suite).
  4. 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_key for the stream variant — it's webhook-only.

Credential mapping

im_apps columnFeishu meaning
provider"feishu"
mode"stream" (recommended) or "webhook"
app_idFeishu app_id (cli_…).
app_secretFeishu app_secret.
verify_tokenOptional. Used by webhook-mode signature verification.
encrypt_keyRequired in webhook mode, ignored in stream mode.
allow_fromOptional. 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

  1. 开发者后台 → 创建企业自建应用. Get app_id + app_secret.
  2. 应用功能 → 机器人 → enable. Add the bot to your test chat.
  3. 权限管理 → 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_botSendText / EditText.
  4. 事件订阅 → 长连接模式 (long-connection) → enable.
  5. In Ongrid: Settings → IM bridge → New → Provider = feishu → Mode = stream → paste app_id + app_secret. Save and Enable.
  6. @ 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:

text
sig = sha256(timestamp + nonce + encrypt_key + body), hex-encoded

Yes — 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_from today — the Feishu platform itself is tenant-gated, so only enterprise members reach the bot. If your tenant has guests / outside collaborators, fill allow_from with their open_id values 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 RootId field is captured as ImThread.ImThreadID so replies inside a thread continue the same session — no need for the user to reset context per question.