w7corp/easywechat · error · RuntimeException
-40005
-40005
Error message
Invalid appId.
What it means
After a successful AES decryption, Encryptor::decrypt() reads the 4-byte big-endian content length and requires the remaining tail of the plaintext to equal $this->receiveId (the app_id/corp_id passed to the constructor). A mismatch throws RuntimeException('Invalid appId.', -40005 ERROR_INVALID_APP_ID): the message was encrypted for a different WeChat account than the one you configured.
Source
Thrown at src/Kernel/Encryptor.php:206
if (! hash_equals($signature, $msgSignature)) {
throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE);
}
$plaintext = Pkcs7::unpadding(
openssl_decrypt(
base64_decode($ciphertext, true) ?: '',
'aes-256-cbc',
$this->aesKey,
OPENSSL_NO_PADDING,
iv: substr($this->aesKey, 0, self::BLOCK_SIZE)
) ?: '',
blockSize: strlen($this->aesKey)
);
$plaintext = substr($plaintext, self::BLOCK_SIZE);
$contentLength = (unpack('N', substr($plaintext, 0, 4)) ?: [])[1];
if ($this->receiveId && trim(substr($plaintext, $contentLength + 4)) !== $this->receiveId) {
throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID);
}
return substr($plaintext, 4, $contentLength);
}
}
View on GitHub (pinned to f0cf0a8b83)
Solutions
- Verify the app_id (or corp_id / component app_id) used to build the Encryptor is the same account that WeChat encrypted the callback for
- For OpenPlatform callbacks, make sure you use the authorizer's app_id context, not the open platform component app_id, when constructing the decryptor
- Confirm the aes_key belongs to the same account — a wrong key decrypts to garbage and corrupts the length/tail parsing
Defensive patterns
Strategy: try-catch
Try / catch
try {
$message = $app->getServer()->handle($request);
} catch (\EasyWeChat\Kernel\Exceptions\RuntimeException $e) {
if ((int) $e->getCode() === \EasyWeChat\Kernel\Encryptor::ERROR_INVALID_APP_ID) {
// callback arrived for a different account than this app is configured with
log_mismatched_callback($request);
return new Response('', 403);
}
throw $e;
} Prevention
- Keep app_id, token and aes_key for one account together in a single config set; never mix values across sets
- Give each WeChat account/env its own callback URL and handler
- For OpenPlatform, route component vs authorizer callbacks to distinct handlers
When it happens
Trigger: An OpenPlatform component callback (authorized appid payload) being decrypted by an Encryptor built with the component app_id, or vice versa; app_id/corp_id from another environment (test vs prod); occasionally a wrong aes_key producing garbage plaintext with a bogus contentLength, making the tail comparison fail.
Common situations: OpenPlatform apps mixing component_id/app_id values, env config bleeding between deployments, one callback endpoint serving several official accounts but a single hardcoded app_id, wrong aes_key after key rotation.
Related errors
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/4ba66b9a55553e18.
Report an issue: GitHub.