Tencent/WeKnora · error

appchat api error: code=%d msg=%s

Error message

appchat api error: code=%d msg=%s

What it means

sendToAppChat posts a reply to a WeCom appchat (group chat) via WeChat Work's appchat send API. When WeCom responds with a non-zero errcode, the adapter surfaces it as "appchat api error: code=%d msg=%s". This is an upstream WeCom API rejection, not a local bug.

Source

Thrown at internal/im/wecom/webhook_adapter.go:336

		return fmt.Errorf("create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("send appchat message: %w", err)
	}
	defer resp.Body.Close()

	var result struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return fmt.Errorf("decode response: %w", err)
	}
	if result.ErrCode != 0 {
		return fmt.Errorf("appchat api error: code=%d msg=%s", result.ErrCode, result.ErrMsg)
	}

	return nil
}

// sendToUser sends a message directly to a user via the application message API.
// Reference: https://developer.work.weixin.qq.com/document/path/90236
func (a *WebhookAdapter) sendToUser(ctx context.Context, accessToken, userID string, reply *im.ReplyMessage) error {
	payload := map[string]interface{}{
		"touser":  userID,
		"msgtype": "markdown",
		"agentid": a.corpAgentID,
		"markdown": map[string]string{
			"content": reply.Content,
		},
	}

	payloadBytes, err := json.Marshal(payload)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the code/msg in the error and look it up in WeCom's API error code docs (e.g. 40014 bad token, 82003 invalid chatid).
  2. Verify the chatid exists and the sending app is a member of that appchat.
  3. Refresh the access token if the code indicates token invalid/expired, then retry.
  4. Confirm the app has appchat send permission in WeCom admin settings.

Example fix

// before
replyTarget = "defunct-chat-id"
// after
replyTarget = chatidVerifiedViaWeComAppchatGet("current-chat-id")
Defensive patterns

Strategy: retry

Validate before calling

// pre-checks before send
if chatID == "" {
    return fmt.Errorf("refusing appchat send: empty chatid")
}
if token stale { /* let getAccessToken refresh; ensure corpid/secret valid */ }

Try / catch

if err := a.sendToAppChat(ctx, chatID, payload); err != nil {
    var code int
    if n, _ := fmt.Sscanf(err.Error(), "appchat api error: code=%d", &code); n == 1 {
        switch code {
        case 40014, 42001: // token invalid/expired
            a.invalidateToken(); return a.sendToAppChat(ctx, chatID, payload)
        default:
            return fmt.Errorf("appchat send rejected by wecom (code %d): %w", code, err)
        }
    }
    return err
}

Prevention

When it happens

Trigger: SendReply routing to an app chat where WeCom returns errcode != 0 — e.g. invalid chatid, bot not a member of the chat, or the appchat API not enabled for the app.

Common situations: Chat was disbanded or chatid changed; the app was removed from the group; using the appchat API with an app that lacks chat permissions; typographic error in the chatid stored on the channel.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/c5f8d253041b6bf7. Report an issue: GitHub.