Tencent/WeKnora · error

decrypt message: %w

Error message

decrypt message: %w

What it means

In the WeCom webhook adapter's ParseCallback, the encrypted payload field (body.Encrypt) is decrypted with the adapter's crypto key after XML unmarshalling. This error wraps any failure of that decryption — almost always a wrong or mismatched EncodingAESKey between your WeCom app config and the adapter's configured key.

Source

Thrown at internal/im/wecom/webhook_adapter.go:208

	return true
}

// ParseCallback parses a WeCom callback into a unified IncomingMessage.
func (a *WebhookAdapter) ParseCallback(c *gin.Context) (*im.IncomingMessage, error) {
	bodyBytes, err := io.ReadAll(c.Request.Body)
	if err != nil {
		return nil, fmt.Errorf("read body: %w", err)
	}

	var body callbackRequestBody
	if err := xml.Unmarshal(bodyBytes, &body); err != nil {
		return nil, fmt.Errorf("unmarshal xml: %w", err)
	}

	// Decrypt the message
	decrypted, err := a.decrypt(body.Encrypt)
	if err != nil {
		return nil, fmt.Errorf("decrypt message: %w", err)
	}

	// Log raw decrypted message for debugging
	logger.Debugf(c.Request.Context(), "[WeCom] Raw decrypted callback: %s", string(decrypted))

	var msg wecomMessage
	if err := xml.Unmarshal(decrypted, &msg); err != nil {
		return nil, fmt.Errorf("unmarshal decrypted message: %w", err)
	}

	logger.Debugf(c.Request.Context(), "[WeCom] Parsed webhook message: msgid=%s msgtype=%s from=%s content=%q picurl=%q mediaid=%q",
		msg.MsgID, msg.MsgType, msg.FromUserName, msg.Content, msg.PicUrl, msg.MediaId)

	// Determine chat type
	chatType := im.ChatTypeDirect
	chatID := ""
	isGroup := msg.ChatID != ""
	if isGroup {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Verify the adapter's configured EncodingAESKey matches the one shown in the WeCom admin console for the exact application receiving the callback.
  2. Re-check that corp_id/token/aes_key triple all belong to the same WeCom app.
  3. Inspect the inner wrapped error (%w chain) to distinguish padding/base64 failures from key mismatch.
  4. If you rotated keys, redeploy/reload the adapter so the new key is used.

Example fix

// before
ch.Credentials = `{"corp_id":"x","token":"t","aes_key":"<old-key>"}`
// after (key copied from WeCom app settings)
ch.Credentials = `{"corp_id":"x","token":"t","aes_key":"<current-encoding-aes-key>"}`
Defensive patterns

Strategy: try-catch

Validate before calling

// before trusting callbacks, confirm key config matches WeCom console
if a.aesKey == "" || len(a.aesKey) != 43 {
    return fmt.Errorf("wecom adapter misconfigured: EncodingAESKey must be the 43-char base64 key from WeCom admin")
}

Try / catch

msg, err := adapter.ParseCallback(c)
if err != nil {
    if strings.Contains(err.Error(), "decrypt message") {
        log.Errorf("wecom callback decryption failed (check EncodingAESKey/corp_id match): %v", err)
        c.String(http.StatusBadRequest, "decrypt failed")
        return
    }
    c.String(http.StatusBadRequest, "bad callback")
}

Prevention

When it happens

Trigger: POSTing a WeCom callback whose <Encrypt> field cannot be decrypted with the configured aes_key — wrong key configured, callback from a different WeCom app/corp, or a tampered/replayed payload.

Common situations: Rotating the EncodingAESKey in the WeCom admin console without updating the channel credentials; copying the key from the wrong application; pointing a test callback at a different corp's callback URL.

Related errors


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