w7corp/easywechat · error · BadRequestException
Request signature must not be empty.
Error message
Request signature must not be empty.
What it means
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.
Source
Thrown at src/Kernel/Traits/DecryptMessage.php:100
*
* @param array<string,mixed> $query
*/
protected function getQueryValue(array $query, string $key): string
{
$value = $query[$key] ?? '';
return is_scalar($value) ? strval($value) : '';
}
/**
* @param array<int, int|string> $params
*
* @throws BadRequestException
*/
protected function assertSignatureMatches(array $params, string $signature): void
{
if (empty($signature)) {
throw new BadRequestException('Request signature must not be empty.');
}
sort($params, SORT_STRING);
if (! hash_equals(sha1(implode($params)), $signature)) {
throw new BadRequestException('Invalid request signature.');
}
}
}
View on GitHub (pinned to f0cf0a8b83)
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.
Example fix
// before (test): GET /callback with no signature params
// after (test): sign like WeChat does
$params = [$token, $ts, $nonce, $cipher];
sort($params, SORT_STRING);
$sig = sha1(implode($params));
// GET /callback?msg_signature={$sig}×tamp={$ts}&nonce={$nonce}&encrypt_type=aes Defensive patterns
Strategy: validation
Validate before calling
$q = $request->getQueryParams();
foreach (['msg_signature', 'timestamp', 'nonce'] as $k) {
if (empty($q[$k]) || !is_scalar($q[$k])) { return new \Nyholm\Psr7\Response(400); }
} Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: nginx try_files / internal redirects losing the query string; hitting the route from Postman without params; a WAF normalizing URLs before forwarding to PHP.
Related errors
- Request ciphertext must not be empty.
- Invalid request signature.
- token or aes_key cannot be empty.
- The token is required to validate the request signature, ple
- -40001
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/8f50e953a52faf05.
Report an issue: GitHub.