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

  1. Reproduce the raw request and capture the response body — it contains code/message telling the real cause (401 = signature/serial, 429 = slow down)
  2. Verify the merchant certificate serial + private key pair configured for the client match the one uploaded in the merchant console
  3. Add backoff-and-retry around 429/5xx; do not retry 4xx
  4. 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

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


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/4fac978cf2a7746f. Report an issue: GitHub.