Issue: JSON Serialization Behavior Change in 0.28.1 causing HTTP Signature Digest Mismatch #3737
Replies: 1 comment
|
Your analysis is spot-on. In json.dumps(payload, separators=(",", ":"))The Difference in Wire BytesPython's standard library import json
import httpx
payload = {"hello": "world", "count": 123}
# Python default json.dumps:
# b'{"hello": "world", "count": 123}' <-- Contains whitespace
# httpx wire bytes (compact):
req = httpx.Request("POST", "https://example.com", json=payload)
# b'{"hello":"world","count":123}' <-- No whitespaceBecause cryptographic signatures (like RFC 3230 / RFC 9530 Digest headers used by CyberSource and payment gateways) hash the exact wire bytes, computing the digest against Recommended Solutions1. Idiomatic: Compute the Digest directly from
|
Uh oh!
There was an error while loading. Please reload this page.
Summary
After upgrading
httpxfrom0.25.0to0.28.1, HTTP Signatures that rely on a Digest of the request body began failing with a 401 Unauthorized (Authentication Failed) error when interacting with the CyberSource API.Root Cause Analysis
The issue appears to correspond to a change in how
httpxgenerates the JSON body bytes when using thejsonargument inclient.request().json.dumps(payload)(using standard Pythonjsonmodule default separators).json.dumps(payload)matched the hash of the body sent byhttpx.json.dumps(payload)did not match the body sent byhttpx.Workaround / Proof
We confirmed the issue by manually serializing the payload to a string using
json.dumps(payload)and passing this string tohttpx.request(..., content=payload_str).content=payload_str, the request succeeded withhttpx0.28.1.json=payload, the request failed corresponding to a digest mismatch.This indicates that
httpx0.28.1 is producing a JSON byte stream that differs from the standard implementation ofjson.dumps(payload)(e.g., changes in whitespace/separators or key ordering), whereas 0.25.0 produced a matching stream.Environment
httpx==0.25.0(Working)httpx==0.28.1(Failing Digest)Recommendation / Request
If
httpxhas intentionally changed default JSON serialization (e.g. to be more compact), it would be helpful to document this behavior, as it breaks compatibility with systems that compute signatures based on standardjson.dumps()output. Alternatively, a flag to control serialization behavior would be beneficial.All reactions