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
- Verify the adapter's configured EncodingAESKey matches the one shown in the WeCom admin console for the exact application receiving the callback.
- Re-check that corp_id/token/aes_key triple all belong to the same WeCom app.
- Inspect the inner wrapped error (%w chain) to distinguish padding/base64 failures from key mismatch.
- 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
- Keep token/aes_key/corp_id sourced from the same WeCom application.
- Re-deploy adapters after key rotation in the WeCom console.
- Alert on decryption failures — they often indicate replay or misconfiguration.
- Log the inner wrapped error, never just the top-level message.
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
- ciphertext too short
- ciphertext length is not a multiple of AES block size
- invalid padding
- plaintext too short
- message length mismatch
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/596092ed8c3a42ce.
Report an issue: GitHub.