{"record":{"id":"82adcdaec9517e22","repo":"w7corp/easywechat","slug":"encrypted-message-is-required-plaintext-message-r","errorCode":null,"errorMessage":"Encrypted message is required, plaintext message rejected.","messagePattern":"Encrypted message is required, plaintext message rejected\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":null,"severity":"error","filePath":"src/OfficialAccount/Server.php","lineNumber":58,"sourceCode":"     * @throws InvalidConfigException\n     */\n    public function serve(): ResponseInterface\n    {\n        $query = $this->getRequest()->getQueryParams();\n\n        if ($str = $this->getQueryValue($query, 'echostr')) {\n            $this->validatePlainRequest($query);\n\n            return new Response(200, [], $str);\n        }\n\n        $message = $this->getRequestMessage($this->getRequest());\n\n        if ($this->encryptor && $this->isEncryptedRequest($query, $message)) {\n            $this->prepend($this->decryptRequestMessage($query));\n        } else {\n            if ($this->requireEncryption) {\n                throw new BadRequestException('Encrypted message is required, plaintext message rejected.');\n            }\n\n            $this->validatePlainRequest($query);\n        }\n\n        $response = $this->handle(new Response(200, [], 'success'), $message);\n\n        if (! ($response instanceof ResponseInterface)) {\n            $response = $this->transformToReply($response, $message, $this->encryptor);\n        }\n\n        return ServerResponse::make($response);\n    }\n\n    /**\n     * @throws Throwable\n     */\n    public function addMessageListener(string $type, callable|string $handler): static","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/OfficialAccount/Server.php#L40-L76","documentation":"Server::serve() rejects an inbound callback when the Server was constructed with requireEncryption=true but the request carries no ciphertext (no encrypt_type=aes marker). It is a deliberate security control: plaintext mode drops encryption protection, so with the flag set EasyWeChat throws BadRequestException instead of processing. Per the class docblock, both secure and compatible mode push encrypt_type=aes — only plaintext mode has neither.","triggerScenarios":"MP console 消息加解密 set to 明文模式 (plaintext) while the Server requires encryption; replaying or hand-crafting a POST to the callback URL without encrypt_type=aes and the Encrypt node; test harnesses posting raw XML.","commonSituations":"Team enables strict encryption in code but never switches the mode in the MP console (or vice versa); different accounts per environment where one is plaintext; curl-based webhook testing against a strict server.","solutions":["Switch the official account to 安全模式 (or compatible mode) in the MP console so pushes carry encrypt_type=aes and the Encrypt node","Or stop requiring encryption: construct the Server without requireEncryption and keep plain signature validation","For local tests, POST a real encrypted envelope (Encrypt node plus encrypt_type=aes&msg_signature query) instead of raw XML"],"exampleFix":"// before: strict server + WeChat account still in plaintext mode\n$server = new \\EasyWeChat\\OfficialAccount\\Server($request, $app->getEncryptor(), $token, requireEncryption: true);\n$response = $server->serve(); // BadRequestException\n\n// after (option A): switch the console to 安全模式 so pushes are encrypted and keep the strict server\n// after (option B): keep the console in plaintext and drop the flag\n$server = new \\EasyWeChat\\OfficialAccount\\Server($request, $app->getEncryptor(), $token);\n$response = $server->serve();","handlingStrategy":"validation","validationCode":"$query = $request->getQueryParams();\n$encrypted = strtolower((string) ($query['encrypt_type'] ?? '')) === 'aes';\nif (! $encrypted) {\n    return new \\Nyholm\\Psr7\\Response(400, [], 'encrypted payloads only');\n}","typeGuard":null,"tryCatchPattern":"try {\n    return $app->getServer()->serve();\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\BadRequestException $e) {\n    // plaintext rejected on purpose: log a security event, return 400\n    return new \\Nyholm\\Psr7\\Response(400, [], 'bad request');\n}","preventionTips":["Keep the MP console message mode and the requireEncryption flag in sync via a documented checklist","Monitor rejects: a sudden wave of plaintext pushes usually means console settings changed","Simulate encrypted envelopes (encrypt_type=aes + msg_signature + Encrypt node) in webhook tests"],"tags":["php","easywechat","wechat","official-account","encryption","webhook","security"],"backgroundTag":"encrypted-payload-required","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}