Larksuite(Feishu)
Feishu(国内版)と Lark Suite(海外版)は同じ OpenAPI 表面を共有します。 Ongrid プロバイダーは両者を 1 つの統合として扱います。テナントに合う ベース URL を選んでください。
| モード | 何をするか |
|---|---|
| 通知 | カスタムボット webhook 経由で Feishu/Lark グループに アラートをプッシュ。 |
| IM ブリッジ | WebSocket 長接続を使った双方向エージェントチャット。 |
通知モード(カスタムボット)
Feishu 送信者は Feishu / Lark カスタムボット ペイロードを、ボット 管理者が提供する webhook URL にポストします。ペイロードの形:
{
"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>"
}署名 —— sign フィールド
Feishu カスタムボットで 签名校验 (signature verification) をオンに すると、Feishu が共有 secret を渡してくれます。送信者は sign フィールドを こう計算します:
stringToSign = timestamp + "\n" + secret
sign = base64(HMAC-SHA256(key=stringToSign, message=<empty>))そう —— secret が HMAC の 鍵素材 と string-to-sign の一部の両方を 担います。これは Feishu がドキュメント化しているアルゴリズムで、 signFeishu が実装するものです。
Secret フィールドを空欄にすると、Ongrid は sign / timestamp なしで ポストします —— ボットの署名検証をオフ(または IP allowlist 経由)に しているときだけ使えます。
セットアップ
- Feishu グループで → 设置 → 群机器人 → 添加机器人 → 自定义机器人。 名前とアバターを設定。
- 签名校验 にチェック、secret をコピー。
- webhook URL をコピー —— こんな感じ:
https://open.feishu.cn/open-apis/bot/v2/hook/<uuid>(Lark Suite なら…/open.larksuite.com/…)。 - Ongrid で:Settings → Channels → New → Provider =
feishu→ Endpoint = webhook URL → Secret = 署名 secret。
カスタムボット ≠ アプリ
通知モードは カスタムボット(グループスコープ、webhook のみ)を 使います。IM ブリッジモードは Feishu アプリ(テナントスコープ、OAuth
- events)を使います。これらは異なる概念で、認証情報は重なりません。
IM ブリッジモード(長接続ストリーム)
双方向ブリッジは公式 github.com/larksuite/oapi-sdk-go/v3/ws クライアント が配信する Feishu 長接続 を使います。manager が Feishu のイベント エンドポイントにダイヤルし、Feishu が WebSocket でイベントをプッシュ します —— 公開 webhook URL は不要です。
なぜ webhook ではなくストリームか
webhook モード(mode=webhook)は後方互換のためスキーマでサポート されていますが、長接続ストリームが推奨パスです:
- manager 上の公開 ingress 不要。
- supervisor の backoff 付き再接続を再利用(SDK が独自の再接続を持ち、 supervisor は終端的な失敗に対する外側のループを追加)。
- SDK が内部で署名検証 + AES 復号を処理するので、ストリーム版で
encrypt_keyを埋める必要はありません —— webhook 専用です。
認証マッピング
im_apps 列 | Feishu での意味 |
|---|---|
provider | "feishu" |
mode | "stream"(推奨)または "webhook" |
app_id | Feishu app_id(cli_…)。 |
app_secret | Feishu app_secret。 |
verify_token | オプション。webhook モードの署名検証で使う。 |
encrypt_key | webhook モードでは必須、ストリームモードでは無視。 |
allow_from | オプション。Feishu はテナントゲートされるので allowlist は必須でない。 |
default_locale
中国語で書く operator の Feishu テナントなら zh、英語の Lark チームなら en。空(デフォルト)は LLM がユーザーをミラーします。
セットアップ
- 开发者后台 → 创建企业自建应用。
app_id+app_secretを取得。 - 应用功能 → 机器人 → 有効化。テストチャットにボットを追加。
- 权限管理 → 付与:
im:message—— ボット宛のメッセージを読む。im:message.group_at_msg—— グループメンションイベント。im:message.p2p_msg—— DM イベント。im:message:send_as_bot——SendText/EditText。
- 事件订阅 → 长连接模式 (long-connection) → 有効化。
- Ongrid で:Settings → IM bridge → New → Provider =
feishu→ Mode =stream→app_id+app_secretを貼り付け。Save して Enable。 - チャットでボットを
@。エージェントが拾います。
tenant_access_token キャッシュ
アウトバウンド呼び出し(SendText、EditText)は tenant_access_token で認証し、有効期限の 200 秒以内になると先回りでリフレッシュします。 トークンは Client インスタンスごとにキャッシュされ、sync.Mutex で 保護されます。認証情報の rotation はクライアントの再構築を意味します。 tenantAccessToken 参照。
edit にも msg_type が必要
Feishu の PUT /open-apis/im/v1/messages/<id> はボディに msg_type を 要求します —— 省略すると code: 99992402(フィールドバリデーション失敗) が返ります(「edit message」のドキュメントページにはそれが明示されて いないにもかかわらず)。プロバイダーは常に msg_type: text を送ります。
Webhook モード(レガシー、互換性のため保持)
webhook モードは UI で選択可能なまま。supervisor が自分で署名を検証し、 ペイロードを復号します。
署名検証
HTTP ハンドラーは X-Lark-Signature を読み、 VerifyEventSignature を走らせます:
sig = sha256(timestamp + nonce + encrypt_key + body), hex-encodedそう —— HMAC ではなく SHA-256 です。encrypt_key がハッシュ入力内で 共有 secret の役割を果たします。verifier は悪い入力で panic することは ありません。不一致は ErrBadSignature を返します。
ペイロード復号
encrypt_key が設定されていると、Feishu はイベント JSON を AES-256-CBC で ラップします。復号は以下を使います:
- 鍵:
SHA-256(encrypt_key)。 - IV:base64 デコードした暗号文の最初の 16 バイト。
- パディング:PKCS#7。
DecryptEvent 参照。
可能ならストリームモードを選んでください
webhook モードには公開 HTTPS エンドポイントと上記の encrypt_key 配管が必要です。長接続ストリームは両方を回避します。webhook モードに 手を伸ばすのは特にそれが必要なときのみ(例:Feishu イベントを既存の 公開 webhook ルーターと統合)。
クセ
- Feishu ストリームクライアントは今日
allow_fromを強制しません —— Feishu プラットフォーム自体がテナントゲートされているので、企業 メンバーのみがボットに届きます。テナントにゲスト / 外部協力者が いるなら、allow_fromに彼らのopen_id値を埋めて会話をさらに ロックダウンしてください。 - スタンプ、ファイル、カード、リッチメッセージは落とされます (
msg_type == "text"だけがエージェントをトリガー)。Telegram / Slack と同じ S1 契約。 RootIdフィールドはImThread.ImThreadIDとして取得されるので、 スレッド内の返信は同じセッションを継続します —— ユーザーは質問ごとに コンテキストをリセットする必要はありません。