openclaw - 💡(How to fix) Fix Feishu card.action.trigger via WebSocket: parseFeishuCardActionEventPayload rejects valid payloads (missing user_id, context field mismatch) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#60749Fetched 2026-04-08 02:47:34
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Author
Participants

Root Cause

parseFeishuCardActionEventPayload() in monitor-*.js requires all of these fields to be non-empty:

if (!token || !openId || !userId || !unionId || !tag || !isRecord(actionValue) || !contextOpenId || !contextUserId || !chatId) return null;

However, the Feishu WebSocket SDK delivers card.action.trigger payloads with a different field structure than the HTTP webhook callback:

FieldHTTP WebhookWebSocket Long-Connection
operator.user_id✅ presentmissing
operator.open_id✅ present✅ present
operator.union_id✅ present✅ present
context.chat_id✅ presentmissing
context.open_chat_id❌ not presentpresent instead
context.open_id✅ presentmissing
context.user_id✅ presentmissing

Fix Action

Workaround

Manually patching parseFeishuCardActionEventPayload() in dist/monitor-*.js with the fallback logic above. This patch is lost on openclaw update.

Code Example

feishu[<accountId>]: ignoring malformed card action payload

---

if (!token || !openId || !userId || !unionId || !tag || !isRecord(actionValue) || !contextOpenId || !contextUserId || !chatId) return null;

---

{
  "schema": "2.0",
  "event_type": "card.action.trigger",
  "operator": {
    "tenant_key": "xxx",
    "open_id": "ou_xxx",
    "union_id": "on_xxx"
  },
  "action": {
    "value": {"command": "some_value"},
    "tag": "button"
  },
  "context": {
    "open_message_id": "om_xxx",
    "open_chat_id": "oc_xxx"
  }
}

---

const userId = readString(operator.user_id) || openId;        // fallback to open_id
const contextOpenId = readString(context.open_id) || openId;   // fallback to operator open_id
const contextUserId = readString(context.user_id) || userId;   // fallback to userId
const chatId = readString(context.chat_id) || readString(context.open_chat_id);  // accept open_chat_id

// Relax the null check — userId and contextOpenId/contextUserId are now derived
if (!token || !openId || !unionId || !tag || !isRecord(actionValue) || !chatId) return null;
RAW_BUFFERClick to expand / collapse

Bug Description

When using Feishu WebSocket long-connection mode (connectionMode: "websocket"), clicking card buttons (interactive cards with action buttons) always results in:

feishu[<accountId>]: ignoring malformed card action payload

The card action is received by the gateway but parseFeishuCardActionEventPayload() returns null, so the callback is silently dropped.

Root Cause

parseFeishuCardActionEventPayload() in monitor-*.js requires all of these fields to be non-empty:

if (!token || !openId || !userId || !unionId || !tag || !isRecord(actionValue) || !contextOpenId || !contextUserId || !chatId) return null;

However, the Feishu WebSocket SDK delivers card.action.trigger payloads with a different field structure than the HTTP webhook callback:

FieldHTTP WebhookWebSocket Long-Connection
operator.user_id✅ presentmissing
operator.open_id✅ present✅ present
operator.union_id✅ present✅ present
context.chat_id✅ presentmissing
context.open_chat_id❌ not presentpresent instead
context.open_id✅ presentmissing
context.user_id✅ presentmissing

Actual WebSocket payload (captured)

{
  "schema": "2.0",
  "event_type": "card.action.trigger",
  "operator": {
    "tenant_key": "xxx",
    "open_id": "ou_xxx",
    "union_id": "on_xxx"
  },
  "action": {
    "value": {"command": "some_value"},
    "tag": "button"
  },
  "context": {
    "open_message_id": "om_xxx",
    "open_chat_id": "oc_xxx"
  }
}

Note: operator.user_id, context.open_id, context.user_id, and context.chat_id are all absent. Only context.open_chat_id is provided (not context.chat_id).

Suggested Fix

In parseFeishuCardActionEventPayload(), use fallbacks for missing fields:

const userId = readString(operator.user_id) || openId;        // fallback to open_id
const contextOpenId = readString(context.open_id) || openId;   // fallback to operator open_id
const contextUserId = readString(context.user_id) || userId;   // fallback to userId
const chatId = readString(context.chat_id) || readString(context.open_chat_id);  // accept open_chat_id

// Relax the null check — userId and contextOpenId/contextUserId are now derived
if (!token || !openId || !unionId || !tag || !isRecord(actionValue) || !chatId) return null;

This maintains backward compatibility with HTTP webhook payloads while also supporting WebSocket payloads.

Impact

  • All card button interactions via WebSocket mode are broken (100% failure rate)
  • Only affects connectionMode: "websocket" — HTTP webhook mode is unaffected
  • The gateway successfully receives the callback but silently drops it

Environment

  • OpenClaw version: 2026.4.2
  • Feishu SDK: lark-oapi (Python 1.5.3 / Node @larksuiteoapi/node-sdk)
  • Connection mode: websocket
  • OS: macOS (arm64)

Workaround

Manually patching parseFeishuCardActionEventPayload() in dist/monitor-*.js with the fallback logic above. This patch is lost on openclaw update.

extent analysis

TL;DR

Update the parseFeishuCardActionEventPayload() function to use fallbacks for missing fields in the WebSocket payload.

Guidance

  • Identify the missing fields in the WebSocket payload, such as operator.user_id, context.open_id, context.user_id, and context.chat_id.
  • Implement fallback logic in parseFeishuCardActionEventPayload() to derive the missing fields from available data, as suggested in the issue.
  • Verify that the updated function correctly handles both HTTP webhook and WebSocket payloads.
  • Consider patching the dist/monitor-*.js file with the updated function, but be aware that this patch will be lost on openclaw update.

Example

const userId = readString(operator.user_id) || openId;
const contextOpenId = readString(context.open_id) || openId;
const contextUserId = readString(context.user_id) || userId;
const chatId = readString(context.chat_id) || readString(context.open_chat_id);

Notes

The suggested fix assumes that the fallback logic is correct and sufficient to handle the missing fields in the WebSocket payload. However, this may not be the case, and additional testing and verification may be necessary to ensure that the updated function works correctly in all scenarios.

Recommendation

Apply the workaround by patching the parseFeishuCardActionEventPayload() function with the fallback logic, as this will allow card button interactions via WebSocket mode to work correctly until a more permanent fix is available.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING