{"record":{"id":"7e0a9724313cb784","repo":"w7corp/easywechat","slug":"failed-to-decode-content-content-must-be-valid-xm","errorCode":null,"errorMessage":"Failed to decode content. Content must be valid XML or JSON.","messagePattern":"Failed to decode content\\. Content must be valid XML or JSON\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Support/MessageParser.php","lineNumber":39,"sourceCode":"        $content = trim($content);\n\n        // Try JSON format first\n        $parsed = json_decode($content, true);\n\n        if (json_last_error() === JSON_ERROR_NONE && is_array($parsed) && ! empty($parsed)) {\n            /** @var array<string, mixed> $parsed */\n            return $parsed;\n        }\n\n        // If JSON decode failed or result is not an array, try XML format\n        $parsed = Xml::parse($content);\n\n        if (is_array($parsed) && ! empty($parsed)) {\n            /** @var array<string, mixed> $parsed */\n            return $parsed;\n        }\n\n        throw new BadRequestException('Failed to decode content. Content must be valid XML or JSON.');\n    }\n}\n","sourceCodeStart":21,"sourceCodeEnd":42,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Support/MessageParser.php#L21-L42","documentation":"MessageParser::parse accepts only content that JSON-decodes to a non-empty array or XML-parses to a non-empty array; anything else throws this BadRequestException. Its main internal use is DecryptMessage::decryptMessage (src/Kernel/Traits/DecryptMessage.php:41), which parses the plaintext of a decrypted push. Garbage almost always means the aes_key was wrong, or the content was never WeChat push data at all.","triggerScenarios":"A decrypted push whose EncodingAESKey does not match the WeChat side, so the plaintext is binary garbage (the signature check only covers token/timestamp/nonce/ciphertext, so a bad aes_key passes it); calling MessageParser::parse() on 'success', an echostr, an HTML error page, '{}' or '[]' (empty arrays are rejected by the ! empty check); a callback route hit by bots or uptime monitors.","commonSituations":"aes_key copied from another app or truncated; monitoring services POSTing to the WeChat callback URL; local tests sending arbitrary strings; proxies returning an HTML error page recorded as the response body.","solutions":["Verify aes_key (43-char EncodingAESKey) matches the WeChat/Work console exactly.","Log the raw input that reaches the parser — if it is 'success'/HTML/bot noise, restrict or protect the callback route.","Ensure the request body reaches the SDK unmodified (no framework re-encoding of XML/JSON).","If you parse arbitrary payloads yourself, pre-check with json_validate()/simplexml_load_string and handle null instead of calling parse()."],"exampleFix":"// before: arbitrary text hits the parser\n$attrs = MessageParser::parse($body);\n// after: pre-check plausibility so non-message traffic is ignored\n$t = trim($body);\nif ($t === '' || ($t[0] !== '<' && $t[0] !== '{')) { return new Response(200, [], 'success'); }\n$attrs = MessageParser::parse($body);","handlingStrategy":"try-catch","validationCode":"$t = trim($body);\n$looksLikeMessage = $t !== '' && ($t[0] === '<' || $t[0] === '{' || $t[0] === '[');","typeGuard":"function isParseableMessageBody(string $body): bool { $t = trim($body); if ($t === '') { return false; } if ($t[0] === '<') { return @simplexml_load_string($t) !== false; } json_decode($t); return json_last_error() === JSON_ERROR_NONE; }","tryCatchPattern":"try { $attrs = \\EasyWeChat\\Kernel\\Support\\MessageParser::parse($plain); } catch (\\EasyWeChat\\Kernel\\Exceptions\\BadRequestException $e) { \\Log::warning('unparseable push payload', ['head' => substr($plain, 0, 64)]); return new \\Nyholm\\Psr7\\Response(200, [], 'success'); }","preventionTips":["Keep callback routes dedicated to WeChat traffic","Log undecryptable payloads together with the aes_key fingerprint (md5 of it) used","Re-compare aes_key with the console after any app migration"],"tags":["php","json","xml","message-parsing","callback"],"backgroundTag":"payload-parse-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}