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

  1. Inspect the errcode: 40014/42001 → force token refresh; 81013/invalid user → verify the userid in WeCom admin.
  2. Confirm the target user is within the app's visible range in WeCom admin console.
  3. Clear the adapter's cached tokenCache if credentials were rotated so a fresh token is fetched.
  4. 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

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


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