w7corp/easywechat · error · BadResponseException

400

400

Error message

Response body is not valid xml.

What it means

Response::toArray() routes to Xml::parse when the content-type is text/xml or application/xml, or the body starts with '<xml>'; if parsing throws, the error is wrapped as BadResponseException('Response body is not valid xml.', 400, $e) with the original parse failure chained as previous. The request itself succeeded — the payload arrived but is not well-formed XML (truncation, HTML error page, stray BOM/whitespace).

Source

Thrown at src/Kernel/HttpClient/Response.php:109

     * @throws BadResponseException
     */
    public function toArray(?bool $throw = null): array
    {
        $throw ??= $this->throw;

        if ('' === $content = $this->response->getContent($throw)) {
            throw new BadResponseException('Response body is empty.');
        }

        $contentType = $this->getHeaderLine('content-type', $throw);

        if (str_contains($contentType, 'text/xml')
            || str_contains($contentType, 'application/xml')
            || str_starts_with($content, '<xml>')) {
            try {
                return Xml::parse($content) ?? [];
            } catch (Throwable $e) {
                throw new BadResponseException('Response body is not valid xml.', 400, $e);
            }
        }

        return $this->response->toArray($throw);
    }

    public function toJson(?bool $throw = null): string|false
    {
        return json_encode($this->toArray($throw), JSON_UNESCAPED_UNICODE);
    }

    /**
     * {@inheritdoc}
     *
     * @throws BadMethodCallException
     */
    public function toStream(?bool $throw = null)
    {

View on GitHub (pinned to f0cf0a8b83)

Solutions

  1. Inspect the previous exception ($e->getPrevious()) and log the raw body — the wrapped error names the exact libxml failure
  2. If a proxy is in front, verify it is not timing out or rewriting error responses (502/504 pages in place of WeChat XML)
  3. Retry the request: malformed bodies from transient gateway errors usually do not recur
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $data = $response->toArray();
} catch (\Symfony\Component\HttpClient\Exception\BadResponseException $e) {
    if (str_contains($e->getMessage(), 'not valid xml')) {
        $raw = $response->getContent(false);
        log_malformed_xml($raw, $e->getPrevious()?->getMessage());
        // transient gateway corruption is common: retry once
        $response = $api->get($sameUrl);
        $data = $response->toArray();
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: A v2 API (XML) response truncated by a proxy/gateway timeout, an HTML error page whose content-type or first bytes look XML-ish, or a payload cut off by body-size limits — Xml::parse then fails on the malformed markup.

Common situations: Nginx/CDN error pages replacing WeChat's response, response_gzip/truncation issues, BOM characters from intermediary layers, WeChat transient error pages.

Related errors


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