w7corp/easywechat · error · RuntimeException

-40012

-40012

Error message

Invalid json data.

What it means

Encryptor::encryptAsJson() assembles the {encrypt, msgsignature, timestamp, nonce} reply envelope and json_encode()s it; if encoding fails (returns false) it throws RuntimeException('Invalid json data.', -40012 ERROR_JSON_BUILD). json_encode only fails on data it cannot serialize — practically, invalid UTF-8 byte sequences in the plaintext that ends up inside the encrypted payload envelope values.

Source

Thrown at src/Kernel/Encryptor.php:129

        return Xml::build($response);
    }

    public function encryptAsJson(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): string
    {
        $encrypted = $this->encryptAsArray($plaintext, $nonce, $timestamp);

        $response = [
            'encrypt' => $encrypted['ciphertext'],
            'msgsignature' => $encrypted['signature'],
            'timestamp' => $encrypted['timestamp'],
            'nonce' => $encrypted['nonce'],
        ];

        $jsonStr = json_encode($response, JSON_UNESCAPED_UNICODE);

        if ($jsonStr === false) {
            throw new RuntimeException('Invalid json data.', self::ERROR_JSON_BUILD);
        }

        return $jsonStr;
    }

    /**
     * @throws RuntimeException
     */
    public function encryptAsArray(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): array
    {
        try {
            $plaintext = Pkcs7::padding(
                random_bytes(self::BLOCK_SIZE).pack('N', strlen($plaintext)).$plaintext.$this->appId,
                blockSize: strlen($this->aesKey)
            );
            $ciphertext = base64_encode(
                openssl_encrypt(
                    $plaintext,

View on GitHub (pinned to f0cf0a8b83)

Solutions

  1. Sanitize/convert the plaintext to valid UTF-8 before encrypting: mb_convert_encoding($plaintext, 'UTF-8', 'UTF-8') drops invalid sequences
  2. Check json_last_error() on your own payload if you build the plaintext JSON yourself before encryption

Example fix

// before
$reply = $encryptor->encrypt($messageWithBrokenUtf8, null, null, 'json');

// after
$clean = mb_convert_encoding($messageWithBrokenUtf8, 'UTF-8', 'UTF-8');
$reply = $encryptor->encrypt($clean, null, null, 'json');
Defensive patterns

Strategy: validation

Validate before calling

// Reject invalid UTF-8 before encrypting JSON replies
$clean = mb_convert_encoding($plaintext, 'UTF-8', 'UTF-8');
if ($clean === null || !mb_check_encoding($clean, 'UTF-8')) {
    throw new InvalidArgumentException('Plaintext must be valid UTF-8');
}
$reply = $encryptor->encrypt($clean, null, null, 'json');

Try / catch

try {
    $json = $encryptor->encryptAsJson($plaintext);
} catch (\EasyWeChat\Kernel\Exceptions\RuntimeException $e) {
    if ($e->getCode() === \EasyWeChat\Kernel\Encryptor::ERROR_JSON_BUILD) {
        // sanitize and retry once
        $json = $encryptor->encryptAsJson(mb_convert_encoding($plaintext, 'UTF-8', 'UTF-8'));
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Calling $encryptor->encrypt($plaintext, $nonce, $timestamp, 'json') (JSON message type, used by WeChat Work callbacks) where $plaintext contains binary or broken-encoded user content (e.g. strings from a latin1 DB column or truncated multibyte input).

Common situations: Mixing encodings (database connection not utf8mb4), user nicknames/remarks with 4-byte emoji mangled by a non-UTF-8 pipeline, binary data accidentally passed as the message body.

Understand the failure class

Related errors


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