Tencent/WeKnora · error
wecom api error: code=%d msg=%s
Error message
wecom api error: code=%d msg=%s
What it means
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.
Source
Thrown at internal/im/wecom/webhook_adapter.go:380
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 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("wecom api error: code=%d msg=%s", result.ErrCode, result.ErrMsg)
}
return nil
}
// getAccessToken retrieves the WeCom access token with caching.
// WeCom tokens expire in 7200 seconds (2 hours); we cache with a safety margin.
func (a *WebhookAdapter) getAccessToken(ctx context.Context) (string, error) {
a.tokenMu.Lock()
defer a.tokenMu.Unlock()
if a.tokenCache != "" && time.Now().Before(a.tokenExpAt) {
return a.tokenCache, nil
}
tokenURL := fmt.Sprintf("%s/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
a.apiBaseURL, a.corpID, a.agentSecret)
View on GitHub (pinned to 988cbb0330)
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.
Example fix
// before
err := a.sendToUser(ctx, "zhang.san-old", msg) // userid no longer exists
// after
userID, err := lookupUserIDByEmail(ctx, "zhang.san@corp.com")
if err == nil {
err = a.sendToUser(ctx, userID, msg)
} Defensive patterns
Strategy: retry
Validate before calling
if userID == "" {
return fmt.Errorf("refusing wecom direct send: empty userid")
} Try / catch
if err := a.sendToUser(ctx, userID, payload); err != nil {
var code int
if n, _ := fmt.Sscanf(err.Error(), "wecom api error: code=%d", &code); n == 1 {
switch {
case code == 40014 || code == 42001:
a.invalidateToken()
return a.sendToUser(ctx, userID, payload) // retry with fresh token
case code == 45009: // rate limited
time.Sleep(backoff); return a.sendToUser(ctx, userID, payload)
default:
return fmt.Errorf("wecom send to %s rejected (code %d): %w", userID, code, err)
}
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- appchat api error: code=%d msg=%s
- failed to generate questions: %w
- get token error: code=%d msg=%s
- ollama API returned status %d: %s
- rerank call failed: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/fccd7aa6dd74ea46.
Report an issue: GitHub.