w7corp/easywechat · error · BadResponseException
Request Failed
Error message
Request Failed
What it means
Thrown by Pay/ResponseValidator::validate() when the WeChat Pay API response carries a non-200 status. This validator is used for specific flows (e.g. after upload/certificate operations) and checks transport-level success before running signature validation, so any 4xx/5xx (401 bad signature, 403, 429 rate limit, 500) surfaces as this generic BadResponseException without the body.
Source
Thrown at src/Pay/ResponseValidator.php:28
use Psr\Http\Message\ResponseInterface as PsrResponse;
class ResponseValidator implements Contracts\ResponseValidator
{
public function __construct(protected MerchantInterface $merchant)
{
}
/**
* @throws BadResponseException
*/
public function validate(PsrResponse|HttpClientResponse $response): void
{
if ($response instanceof HttpClientResponse) {
$response = $response->toPsrResponse();
}
if ($response->getStatusCode() !== 200) {
throw new BadResponseException('Request Failed');
}
(new Validator($this->merchant))->validate($response);
}
}
View on GitHub (pinned to f0cf0a8b83)
Solutions
- Reproduce the raw request and capture the response body — it contains code/message telling the real cause (401 = signature/serial, 429 = slow down)
- Verify the merchant certificate serial + private key pair configured for the client match the one uploaded in the merchant console
- Add backoff-and-retry around 429/5xx; do not retry 4xx
- Sync server time (NTP) since signature includes timestamp and skew yields 401
Example fix
// before
try {
$api->postJson('/v3/...', $payload);
} catch (\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface $e) { /* ... */ }
// after - inspect HTTP status before validating, retry transient failures
$response = $client->request('POST', $url, $options);
if (in_array($response->getStatusCode(), [429, 500, 502, 503], true)) { retryWithBackoff($fn); } Defensive patterns
Strategy: retry
Validate before calling
$status = $response->getStatusCode();
if ($status !== 200) {
$body = $response->getContent(false); // capture code/message
// decide: 4xx -> fail, 429/5xx -> retry
} Try / catch
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
try {
$app->client->postJson($uri, $payload);
} catch (\EasyWeChat\Kernel\Exceptions\BadResponseException $e) {
// inspect upstream body for code/message; only retry on 429/5xx with backoff
} Prevention
- Capture response bodies for non-200 to surface WeChat's code/message
- Rate-limit batch jobs to stay under quotas
- Keep merchant serial/private key consistent and NTP-synced to avoid 401
When it happens
Trigger: Calling the endpoints wrapped by this validator (e.g. marketing/filer-image-upload flows that use ResponseValidator, or any call path where validate() runs) and WeChat Pay answering 401 (Authorization signature wrong), 400 (bad request), 429 (too many requests), or 503.
Common situations: Wrong merchant serial in the Authorization header (401); expired/incorrect merchant private key; hammering the API past rate limits (429) during batch jobs; WeChat Pay incidents returning 5xx; clock skew breaking the signature timestamp.
Related errors
- Encrypt AES ECB failed.
- Read the $certificate failed, please check it whether or nor
- Missing platform certificate.
- Missing V2 API key.
- Invalid platform certficate.
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/4fac978cf2a7746f.
Report an issue: GitHub.