multica-ai/multica · warning

wecom_credentials_unverifiable

wecom_credentials_unverifiable

Error message

wecom: could not reach WeCom to verify this bot

What it means

WeCom credential probe error: the probe could not reach WeCom or got an answer it cannot attribute to the credentials — dial failure, handshake timeout, network down, or any non-zero WeCom error code NOT on the rejection whitelist (e.g. frequency/concurrency protections 45009, 45033). Deliberately distinct from rejection: blaming the credentials would push an admin to rotate a long-connection secret that was fine, and a rotated secret cannot be recovered.

Solutions

  1. Verify network egress from the server to WeCom API endpoints (curl the probe URL directly), fix proxy/firewall/DNS, then retry.
  2. If throttled (45009/45033), wait before retrying — do not rotate the secret.
  3. Only treat credentials as wrong when ErrCredentialsRejected is returned; for this error, keep the pair and fix reachability.

Example fix

// before
err := probe.Check(ctx, botID, secret)
if err != nil {
	rotateSecretPrompt(w) // WRONG: sends admin to rotate a good secret
}

// after
err := probe.Check(ctx, botID, secret)
if errors.Is(err, wecom.ErrCredentialsUnverifiable) {
	// network/platform issue — never suggest rotating; the rotated secret is unrecoverable
	respond(w, 502, "could not reach WeCom to verify — check network and retry later")
	return
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight reachability so install fails fast with a clear cause
if err := probe.ReachWeCom(ctx, 3*time.Second); err != nil {
	return respondBadGateway(w, "server cannot reach WeCom — fix egress before entering credentials")
}

Try / catch

err := probe.Check(ctx, botID, secret)
if err != nil {
	if errors.Is(err, wecom.ErrCredentialsUnverifiable) {
		// transport/throttle — retry with backoff; NEVER suggest rotating the secret
		return retryWithBackoff(ctx, probe.Check, botID, secret)
	}
	return err
}

Prevention

When it happens

Trigger: Running the credential probe when the Multica server cannot reach WeCom endpoints (egress blocked, DNS, proxy), or when WeCom answers with a throttling/platform error (45009, 45033) instead of a credential verdict. The whitelist-plus-fail-closed design maps all of these here.

Common situations: Server behind a firewall without egress to WeCom; retrying the probe rapidly and tripping rate limits; transient WeCom-side outage; corporate proxy terminating the connection.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/9dee58808ac1bf71. Report an issue: GitHub.

Appendix: source

Thrown at server/internal/integrations/wecom/credential_probe.go:60

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
)

// ErrCredentialsRejected is WeCom saying the pair is not valid: a wrong
// secret, a bot that no longer exists, a bot whose API mode is off. It is the
// answer that must reach the admin, because it is the one they can act on.
var ErrCredentialsRejected = errors.New("wecom: WeCom rejected this bot id and secret")

// ErrCredentialsUnverifiable is everything else — the dial failed, the
// handshake timed out, the network is down. Distinct from rejection on
// purpose: telling an admin their credentials are wrong when the deployment
// simply could not reach WeCom sends them to rotate a secret that was fine.
var ErrCredentialsUnverifiable = errors.New("wecom: could not reach WeCom to verify this bot")

// rejectionErrCodes are the WeCom global error codes (document/path/90313)
// documented as a refusal of the credential pair itself — the only answers
// entitled to tell an admin their Bot ID or secret is wrong.
//
// Everything else non-zero is ErrCredentialsUnverifiable, deliberately. WeCom
// only guarantees that 0 means success; the subscribe path is also under
// frequency and concurrency protection (45009, 45033), and the platform can
// fail on its own account. Reading any non-zero code as "wrong secret" pushes
// an admin to rotate a long-connection secret that was fine, and a rotated one
// cannot be recovered — the exact damage this file exists to prevent. So the
// list is a whitelist and the default is fail-closed: refuse the install, keep
// the stored credentials untouched, and say we could not verify.
//
// Codes only, never errmsg text: the message is human-facing Chinese prose
// that WeCom is free to reword.
var rejectionErrCodes = map[int]struct{}{
	40001: {}, // 不合法的secret参数 — the secret does not match this bot

View on GitHub (pinned to 2c0912b6ec)