JeffreySu/WeiXinMPSDK · error · OpenHardwareCallbackCryptException

OpenHardwareCallbackCryptException (decrypt failed…

Error message

OpenHardwareCallbackCryptException (decrypt failed, WXBizMsgCrypt error code)

What it means

After extracting the encrypt field, DecryptAndParse delegates AES decryption to WXBizMsgCrypt.DecryptJsonMsg. Any non-zero return code (signature mismatch, bad AES key, wrong receiveId, malformed ciphertext) is wrapped into OpenHardwareCallbackCryptException carrying the SDK error code. It means the callback could not be authenticated or decrypted with the configured credentials.

Solutions

  1. Compare the exception's error code with WXBizMsgCrypt docs: signature mismatch (invalid signature) → check token; decrypt failure → check EncodingAESKey; receiveId mismatch → check the receiveId parameter.
  2. Copy Token/EncodingAESKey exactly from the open-hardware callback config page; avoid trailing whitespace or Base64 key truncation.
  3. Make receiveId dynamic (resolve per-request from tousername/corpId) instead of hard-coding a single tenant.
  4. Log timestamp/nonce and ensure callbacks are processed promptly; reject/retry stale messages cleanly.
  5. Catch OpenHardwareCallbackCryptException and return a non-2xx only after verifying it is a genuine key mismatch, to avoid breaking WeChat's retry logic.

Example fix

// before
var result = handler.DecryptAndParse(body, token, aesKey, "corpX"); // throws on key mismatch
// after
try
{
    var result = handler.DecryptAndParse(body, token, aesKey, receiveId);
}
catch (OpenHardwareCallbackCryptException ex)
{
    logger.LogWarning(ex, "Callback decrypt failed, code {Code}", ex.ErrorCode);
    return Results.StatusCode(400);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(encodingAesKey) || string.IsNullOrWhiteSpace(receiveId))
    throw new InvalidOperationException("Callback token/AESKey/receiveId must be configured");

Type guard

static bool CredentialsConfigured(CallbackOptions o) =>
    !string.IsNullOrWhiteSpace(o.Token) && o.EncodingAesKey?.Length == 43 && !string.IsNullOrWhiteSpace(o.ReceiveId);

Try / catch

try { var result = handler.DecryptAndParse(body, token, aesKey, receiveId); }
catch (OpenHardwareCallbackCryptException ex)
{
    logger.LogWarning("Callback decrypt failed, WXBizMsgCrypt code {Code}", ex.ErrorCode);
    return Results.StatusCode(400);
}

Prevention

When it happens

Trigger: Token or EncodingAESKey in code differs from those configured in the WeChat Work open-hardware app; receiveId (corpId/appId) mismatch; the msg_signature/timestamp/nonce from the request don't match the body (replayed or tampered callback); wrong secret copied from a different app.

Common situations: Rotating the AES key in the admin console but not in the app; multiple WeChat Work tenants posting to one endpoint with a hard-coded receiveId; message replaying after >5 minutes failing signature freshness checks.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12). Data as JSON: /api/errors/0cdc3058959772cd. Report an issue: GitHub.

Appendix: source

Thrown at src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/OpenHardware/OpenHardwareCallbackHandler.cs:70

            EnsureNotEmpty(nonce, nameof(nonce));
            EnsureNotEmpty(encryptedBody, nameof(encryptedBody));

            var envelope = JsonConvert
                .DeserializeObject<OpenHardwareEncryptedCallbackRequest>(encryptedBody);
            if (envelope == null || string.IsNullOrWhiteSpace(envelope.encrypt))
            {
                throw new ArgumentException(
                    "开放硬件回调正文必须包含非空 encrypt 字段。",
                    nameof(encryptedBody));
            }

            var plaintext = string.Empty;
            var crypt = new WXBizMsgCrypt(token, encodingAesKey, receiveId);
            var errorCode = crypt.DecryptJsonMsg(msgSignature, timestamp, nonce,
                envelope.encrypt, ref plaintext);
            if (errorCode != 0)
            {
                throw new OpenHardwareCallbackCryptException(errorCode);
            }

            return new OpenHardwareCallbackParseResult
            {
                tousername = envelope.tousername,
                plaintext = plaintext,
                message = ParsePlaintext(plaintext)
            };
        }

        /// <summary>
        /// 将已解密的开放硬件 JSON 按 event_type 或 command_type 分派为强类型消息。
        /// </summary>
        /// <param name="plaintext">验签解密后的完整 JSON 文本。</param>
        /// <returns>已识别的事件或指令;未识别类型会保留原始 JSON。</returns>
        /// <exception cref="ArgumentException">明文为空时抛出。</exception>
        /// <exception cref="JsonReaderException">明文不是合法 JSON 时抛出。</exception>
        public static OpenHardwareCallbackMessageBase ParsePlaintext(

View on GitHub (pinned to be573f6f94)