Skip to content

Larksuite (Feishu)

Feishu (国内版) 와 Lark Suite (海外版) 는 동일한 OpenAPI 표면을 공유합니다. Ongrid provider 는 둘을 단일 통합으로 취급합니다; 테넌트에 맞는 base URL 을 선택하세요.

Mode동작
알림커스텀 봇 webhook 으로 Feishu/Lark 그룹에 알림 push.
IM 브릿지WebSocket long-connection 을 사용한 양방향 에이전트 채팅.

알림 모드 (커스텀 봇)

Feishu sender 는 봇 관리자가 제공한 webhook URL 에 Feishu / Lark 커스텀 봇 payload 를 게시합니다. payload 형태:

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>"
}

서명 — sign 필드

Feishu 커스텀 봇에서 签名校验 (signature verification) 를 켜면 Feishu 가 공유 시크릿을 줍니다. sender 는 sign 필드를 다음과 같이 계산합니다:

text
stringToSign = timestamp + "\n" + secret
sign         = base64(HMAC-SHA256(key=stringToSign, message=<empty>))

예 — 시크릿이 HMAC 의 키 자료 와 string-to-sign 의 일부 역할을 둘 다 합니다. 이는 Feishu 가 문서화한 알고리즘이며 signFeishu 가 구현하는 것입니다.

Secret 필드를 비우면 Ongrid 는 sign / timestamp 없이 POST 합니다 — 봇에 서명 검증이 꺼져 있을 (또는 IP 화이트리스트가 대신 있는) 때만 사용 가능.

설정

  1. Feishu 그룹에서 → 设置 → 群机器人 → 添加机器人 → 自定义机器人. 이름과 아바타 부여.
  2. 签名校验 체크; 시크릿 복사.
  3. webhook URL 복사 — 다음과 같이 생김: https://open.feishu.cn/open-apis/bot/v2/hook/<uuid> (Lark Suite 는 …/open.larksuite.com/…).
  4. Ongrid 에서: Settings → Channels → New → Provider = feishu → Endpoint = webhook URL → Secret = 서명 시크릿.

커스텀 봇 ≠ 앱

알림 모드는 커스텀 봇 (그룹 범위, webhook 전용) 을 사용합니다. IM 브릿지 모드는 Feishu (테넌트 범위, OAuth + 이벤트) 을 사용합니다. 이들은 다른 개념입니다; 자격 증명이 겹치지 않습니다.

IM 브릿지 모드 (long-connection 스트림)

양방향 브릿지는 공식 github.com/larksuite/oapi-sdk-go/v3/ws 클라이언트가 전달하는 Feishu long-connection 을 사용합니다. 매니저가 Feishu 의 이벤트 엔드포인트로 다이얼하면 Feishu 가 WebSocket 으로 이벤트를 push 합니다 — 공개 webhook URL 불필요.

왜 스트림이고 webhook 이 아닌가

webhook 모드 (mode=webhook) 는 하위 호환을 위해 스키마에서 지원되지만 long-connection 스트림이 권장 경로입니다.

  • 매니저에 공개 ingress 불필요.
  • supervisor 의 reconnect-with-backoff 재사용 (SDK 자체 reconnect 가 있지만 supervisor 는 터미널 실패용 외부 루프 추가).
  • SDK 가 내부적으로 서명 검증 + AES 복호화를 처리하므로 스트림 변종에 encrypt_key 를 채울 필요 없음 — webhook 전용입니다.

자격 증명 매핑

im_apps columnFeishu 의미
provider"feishu"
mode"stream" (권장) 또는 "webhook"
app_idFeishu app_id (cli_…).
app_secretFeishu app_secret.
verify_token선택. webhook 모드 서명 검증에 사용.
encrypt_keywebhook 모드에서 필수, 스트림 모드에서 무시.
allow_from선택. Feishu 는 테넌트 게이트라서 화이트리스트는 비필수.

default_locale

운영자가 중국어를 쓰는 Feishu 테넌트는 zh, 영어 Lark 팀은 en 으로 설정. 비어 있음 (기본) 은 LLM 이 사용자를 미러링.

설정

  1. 开发者后台 → 创建企业自建应用. app_id + app_secret 획득.
  2. 应用功能 → 机器人 → 활성화. 테스트 채팅에 봇 추가.
  3. 权限管理 → 부여:
    • im:message — 봇에 주소된 메시지 읽기.
    • im:message.group_at_msg — 그룹 멘션 이벤트.
    • im:message.p2p_msg — DM 이벤트.
    • im:message:send_as_botSendText / EditText.
  4. 事件订阅 → 长连接模式 (long-connection) → 활성화.
  5. Ongrid 에서: Settings → IM bridge → New → Provider = feishu → Mode = streamapp_id + app_secret 붙여넣기. 저장 후 Enable.
  6. 채팅에서 봇을 @. 에이전트가 픽업.

tenant_access_token 캐싱

아웃바운드 호출 (SendText, EditText) 은 만료 200 초 이내일 때 적극 갱신되는 tenant_access_token 으로 인증합니다. 토큰은 클라이언트 인스턴스별로 캐시되며 sync.Mutex 로 보호됩니다; 자격 증명 회전은 클라이언트 재구축을 의미합니다. tenantAccessToken 참고.

편집에도 msg_type 이 필수

Feishu 의 PUT /open-apis/im/v1/messages/<id> 는 body 에 msg_type 이 필요합니다 — 누락 시 code: 99992402 (field validation failed) 를 반환합니다 (문서 "edit message" 페이지는 이를 명확히 하지 않음). provider 는 항상 msg_type: text 를 보냅니다.

Webhook 모드 (레거시, 호환성을 위해 유지)

webhook 모드는 여전히 UI 에서 선택 가능. supervisor 가 직접 서명을 검증하고 payload 를 복호화합니다.

서명 검증

HTTP 핸들러가 X-Lark-Signature 를 읽고 VerifyEventSignature 실행:

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

예 — SHA-256, HMAC 아님. encrypt_key 가 해시 입력 안에서 공유 시크릿 역할. verifier 는 잘못된 입력에 절대 panic 하지 않습니다; 불일치는 ErrBadSignature 를 반환.

Payload 복호화

encrypt_key 가 설정되면 Feishu 는 이벤트 JSON 을 AES-256-CBC 로 래핑합니다. 복호화는:

  • Key: SHA-256(encrypt_key).
  • IV: base64 디코드된 ciphertext 의 첫 16 바이트.
  • Padding: PKCS#7.

DecryptEvent 참고.

가능하면 스트림 모드 선택

webhook 모드는 공개 HTTPS 엔드포인트와 위의 encrypt_key 배선이 필요합니다. long-connection 스트림은 둘 다 회피합니다. 특별히 필요할 때만 (예: 기존 공개 webhook 라우터와 Feishu 이벤트 통합) webhook 모드에 손을 뻗으세요.

특이점

  • Feishu 스트림 클라이언트는 오늘 allow_from 을 강제하지 않습니다 — Feishu 플랫폼 자체가 테넌트 게이트라서 enterprise 멤버만 봇에 도달합니다. 테넌트에 게스트 / 외부 협력자가 있다면 그들의 open_id 값으로 allow_from 을 채워 대화를 더 잠그세요.
  • 스티커, 파일, 카드, 리치 메시지는 버려집니다 (msg_type == "text" 만 에이전트를 트리거). Telegram / Slack 과 같은 S1 계약.
  • RootId 필드는 ImThread.ImThreadID 로 캡처되어 스레드 내 응답이 같은 세션을 잇습니다 — 사용자가 질문마다 컨텍스트를 리셋할 필요 없음.