{"record":{"id":"8f50e953a52faf05","repo":"w7corp/easywechat","slug":"request-signature-must-not-be-empty","errorCode":null,"errorMessage":"Request signature must not be empty.","messagePattern":"Request signature must not be empty\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Traits/DecryptMessage.php","lineNumber":100,"sourceCode":"     *\n     * @param  array<string,mixed>  $query\n     */\n    protected function getQueryValue(array $query, string $key): string\n    {\n        $value = $query[$key] ?? '';\n\n        return is_scalar($value) ? strval($value) : '';\n    }\n\n    /**\n     * @param  array<int, int|string>  $params\n     *\n     * @throws BadRequestException\n     */\n    protected function assertSignatureMatches(array $params, string $signature): void\n    {\n        if (empty($signature)) {\n            throw new BadRequestException('Request signature must not be empty.');\n        }\n\n        sort($params, SORT_STRING);\n\n        if (! hash_equals(sha1(implode($params)), $signature)) {\n            throw new BadRequestException('Invalid request signature.');\n        }\n    }\n}\n","sourceCodeStart":82,"sourceCodeEnd":110,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Traits/DecryptMessage.php#L82-L110","documentation":"assertSignatureMatches requires a non-empty signature; the servers read it from the query string — msg_signature for encrypted traffic (src/OfficialAccount/Server.php:205, src/Work/Server.php:211) and signature for echostr/plain validation. getQueryValue() returns '' for missing or non-scalar values, so a rewritten URL without its query string, or ?msg_signature[]=x, yields the empty-signature error before any hash is computed.","triggerScenarios":"A reverse proxy or router rewrite that drops the callback URL's query string; local tests calling the callback route without msg_signature/timestamp/nonce; receiving the signature in the POST body instead of the URL; array-typed query params produced by malformed rewrites.","commonSituations":"nginx try_files / internal redirects losing the query string; hitting the route from Postman without params; a WAF normalizing URLs before forwarding to PHP.","solutions":["Forward the complete original query string (msg_signature, timestamp, nonce) to the SDK.","Check proxy/router rewrite rules preserve '?' — log $request->getUri()->getQuery() once to verify.","Use the callback URL exactly as configured in the WeChat console.","In tests, generate a valid msg_signature (sha1 of sorted token/timestamp/nonce/ciphertext) instead of omitting it."],"exampleFix":"// before (test): GET /callback with no signature params\n// after (test): sign like WeChat does\n$params = [$token, $ts, $nonce, $cipher];\nsort($params, SORT_STRING);\n$sig = sha1(implode($params));\n// GET /callback?msg_signature={$sig}&timestamp={$ts}&nonce={$nonce}&encrypt_type=aes","handlingStrategy":"validation","validationCode":"$q = $request->getQueryParams();\nforeach (['msg_signature', 'timestamp', 'nonce'] as $k) {\n    if (empty($q[$k]) || !is_scalar($q[$k])) { return new \\Nyholm\\Psr7\\Response(400); }\n}","typeGuard":null,"tryCatchPattern":"try { $response = $server->serve(); } catch (\\EasyWeChat\\Kernel\\Exceptions\\BadRequestException $e) { if (str_contains($e->getMessage(), 'signature must not be empty')) { \\Log::warning('callback without signature', ['query' => $request->getUri()->getQuery()]); return new \\Nyholm\\Psr7\\Response(400); } throw $e; }","preventionTips":["Preserve query strings in proxy/router rewrites","Alert on empty query for callback routes","Mirror WeChat's signing algorithm in test helpers instead of omitting params"],"tags":["php","callback","signature","query-string","wechat"],"backgroundTag":"missing-signature","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}