{"record":{"id":"c5f8d253041b6bf7","repo":"Tencent/WeKnora","slug":"appchat-api-error-code-d-msg-s","errorCode":null,"errorMessage":"appchat api error: code=%d msg=%s","messagePattern":"appchat api error: code=(.+?) msg=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/wecom/webhook_adapter.go","lineNumber":336,"sourceCode":"\t\treturn fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"send appchat message: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar result struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn fmt.Errorf(\"decode response: %w\", err)\n\t}\n\tif result.ErrCode != 0 {\n\t\treturn fmt.Errorf(\"appchat api error: code=%d msg=%s\", result.ErrCode, result.ErrMsg)\n\t}\n\n\treturn nil\n}\n\n// sendToUser sends a message directly to a user via the application message API.\n// Reference: https://developer.work.weixin.qq.com/document/path/90236\nfunc (a *WebhookAdapter) sendToUser(ctx context.Context, accessToken, userID string, reply *im.ReplyMessage) error {\n\tpayload := map[string]interface{}{\n\t\t\"touser\":  userID,\n\t\t\"msgtype\": \"markdown\",\n\t\t\"agentid\": a.corpAgentID,\n\t\t\"markdown\": map[string]string{\n\t\t\t\"content\": reply.Content,\n\t\t},\n\t}\n\n\tpayloadBytes, err := json.Marshal(payload)","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/wecom/webhook_adapter.go#L318-L354","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Verify the chatid exists and the sending app is a member of that appchat.","Refresh the access token if the code indicates token invalid/expired, then retry.","Confirm the app has appchat send permission in WeCom admin settings."],"exampleFix":"// before\nreplyTarget = \"defunct-chat-id\"\n// after\nreplyTarget = chatidVerifiedViaWeComAppchatGet(\"current-chat-id\")","handlingStrategy":"retry","validationCode":"// pre-checks before send\nif chatID == \"\" {\n    return fmt.Errorf(\"refusing appchat send: empty chatid\")\n}\nif token stale { /* let getAccessToken refresh; ensure corpid/secret valid */ }","typeGuard":null,"tryCatchPattern":"if err := a.sendToAppChat(ctx, chatID, payload); err != nil {\n    var code int\n    if n, _ := fmt.Sscanf(err.Error(), \"appchat api error: code=%d\", &code); n == 1 {\n        switch code {\n        case 40014, 42001: // token invalid/expired\n            a.invalidateToken(); return a.sendToAppChat(ctx, chatID, payload)\n        default:\n            return fmt.Errorf(\"appchat send rejected by wecom (code %d): %w\", code, err)\n        }\n    }\n    return err\n}","preventionTips":["Validate chatid exists (appchat/get) before first send and on failures.","Keep the app as an active member of target appchats.","Implement token-refresh-on-40xxx with bounded retries.","Map WeCom errcodes to actionable messages in your monitoring."],"tags":["wecom","api-error","network"],"backgroundTag":"upstream-api-error","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}