chenhg5/cc-connect · error

dingtalk: proactive send failed: status=%d, body=%s

Error message

dingtalk: proactive send failed: status=%d, body=%s

What it means

The DingTalk proactive-send API responded with a non-200 HTTP status. The error includes the numeric status and the raw response body so the DingTalk-side failure reason (invalid token, invalid robot code, bad conversationId, rate limit) can be diagnosed. This is a server-rejected request, not a transport failure.

Source

Thrown at platform/dingtalk/dingtalk.go:1698

		return fmt.Errorf("dingtalk: marshal proactive message: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("dingtalk: create proactive request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-acs-dingtalk-access-token", token)

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("dingtalk: proactive send request: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	respBody, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("dingtalk: proactive send failed: status=%d, body=%s", resp.StatusCode, string(respBody))
	}

	slog.Debug("dingtalk: proactive message sent", "api", apiURL, "status", resp.StatusCode)
	return nil
}

var atUserIDRegexp = regexp.MustCompile(`@(\d{4,})`)

// extractAtUserIds extracts @userId patterns from content for DingTalk's atUserIds field.
// Matches @ followed by numeric DingTalk user IDs (e.g. @194252073827812352).
func extractAtUserIds(content string) []string {
	matches := atUserIDRegexp.FindAllStringSubmatch(content, -1)
	seen := make(map[string]bool)
	var ids []string
	for _, m := range matches {
		if len(m) > 1 && !seen[m[1]] {
			seen[m[1]] = true
			ids = append(ids, m[1])

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the status and body in the error message; DingTalk bodies contain errcode/errmsg pinpointing the cause.
  2. If status is 401/invalid token: refresh the access token (it expires ~2h) and retry; verify appKey/appSecret used to get the token.
  3. Verify robotCode matches the app's robot, and that the bot is a member of the target group for group sends.
  4. Check the conversationId/staffId values are current; re-capture them from a live inbound message.
  5. If status is 429 or body mentions limit, slow down and add backoff retry.

Example fix

// before
token := p.tokenCache.Get() // may be hours old
p.sendProactive(ctx, token, requestBody)
// after
token, err := p.getAccessToken(ctx) // refreshes when near expiry
if err != nil {
    return fmt.Errorf("dingtalk: refresh token: %w", err)
}
p.sendProactive(ctx, token, requestBody)
Defensive patterns

Strategy: try-catch

Try / catch

err := p.Send(ctx, rc, msg)
if err != nil {
    var apiErr struct{ Status int; Body string }
    if strings.Contains(err.Error(), "proactive send failed: status=") {
        slog.Error("dingtalk rejected proactive send", "err", err) // body has errcode/errmsg
        if strings.Contains(err.Error(), "invalidAuthentication") {
            // refresh token and retry once
        }
    }
}

Prevention

When it happens

Trigger: The POST to the DingTalk proactive-send endpoint completed but resp.StatusCode != 200 — e.g. expired/invalid access token (invalidAuthentication), wrong robotCode, unknown conversationId/staffId, or exceeded QPS limits.

Common situations: Access token cached past its 2-hour expiry; robot not added to the target group so the conversationId is unauthorized; typo'd staffId; hitting DingTalk API rate limits during bulk notifications.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/737d9ab793bc0903. Report an issue: GitHub.