{"record":{"id":"fccd7aa6dd74ea46","repo":"Tencent/WeKnora","slug":"wecom-api-error-code-d-msg-s","errorCode":null,"errorMessage":"wecom api error: code=%d msg=%s","messagePattern":"wecom api error: code=(.+?) msg=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/wecom/webhook_adapter.go","lineNumber":380,"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 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(\"wecom api error: code=%d msg=%s\", result.ErrCode, result.ErrMsg)\n\t}\n\n\treturn nil\n}\n\n// getAccessToken retrieves the WeCom access token with caching.\n// WeCom tokens expire in 7200 seconds (2 hours); we cache with a safety margin.\nfunc (a *WebhookAdapter) getAccessToken(ctx context.Context) (string, error) {\n\ta.tokenMu.Lock()\n\tdefer a.tokenMu.Unlock()\n\n\tif a.tokenCache != \"\" && time.Now().Before(a.tokenExpAt) {\n\t\treturn a.tokenCache, nil\n\t}\n\n\ttokenURL := fmt.Sprintf(\"%s/cgi-bin/gettoken?corpid=%s&corpsecret=%s\",\n\t\ta.apiBaseURL, a.corpID, a.agentSecret)\n","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/wecom/webhook_adapter.go#L362-L398","documentation":"sendToUser sends a direct message to a WeCom user via the application message API and surfaces any non-zero errcode from WeCom as \"wecom api error: code=%d msg=%s\". It signals that WeCom rejected the send request — commonly an invalid userid, expired access token, or missing send permission.","triggerScenarios":"SendReply delivering to an individual user when WeCom returns errcode != 0 — invalid target userid, access token expired/invalid (e.g. 40014/42001), or app lacking visible-range permission for that user.","commonSituations":"Employee left or userid changed; user outside the app's visible scope; cached access token invalidated after secret rotation; send attempted outside app permission scope.","solutions":["Inspect the errcode: 40014/42001 → force token refresh; 81013/invalid user → verify the userid in WeCom admin.","Confirm the target user is within the app's visible range in WeCom admin console.","Clear the adapter's cached tokenCache if credentials were rotated so a fresh token is fetched.","Retry after fixing; if transient (e.g. 45009 rate limit), back off and retry."],"exampleFix":"// before\nerr := a.sendToUser(ctx, \"zhang.san-old\", msg) // userid no longer exists\n// after\nuserID, err := lookupUserIDByEmail(ctx, \"zhang.san@corp.com\")\nif err == nil {\n    err = a.sendToUser(ctx, userID, msg)\n}","handlingStrategy":"retry","validationCode":"if userID == \"\" {\n    return fmt.Errorf(\"refusing wecom direct send: empty userid\")\n}","typeGuard":null,"tryCatchPattern":"if err := a.sendToUser(ctx, userID, payload); err != nil {\n    var code int\n    if n, _ := fmt.Sscanf(err.Error(), \"wecom api error: code=%d\", &code); n == 1 {\n        switch {\n        case code == 40014 || code == 42001:\n            a.invalidateToken()\n            return a.sendToUser(ctx, userID, payload) // retry with fresh token\n        case code == 45009: // rate limited\n            time.Sleep(backoff); return a.sendToUser(ctx, userID, payload)\n        default:\n            return fmt.Errorf(\"wecom send to %s rejected (code %d): %w\", userID, code, err)\n        }\n    }\n    return err\n}","preventionTips":["Verify userids periodically against the WeCom user list (departures invalidate them).","Keep target users within the app's visible range.","Auto-refresh tokens on 40014/42001 instead of surfacing to users.","Add per-app rate limiting to avoid 45009 throttling errors."],"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"}