fix: use default JSON separators for webhook HMAC signing#152
fix: use default JSON separators for webhook HMAC signing#152EmmaLouise2018 merged 3 commits intomainfrom
Conversation
get_adcp_signed_headers_for_webhook() was signing over compact JSON (no spaces) but callers send the body via json= kwarg which uses default separators (with spaces), causing HMAC verification failures on receivers. Aligns with JS reference implementation's JSON.stringify(). Fixes #151
bokelley
left a comment
There was a problem hiding this comment.
The signing fix is correct — json.dumps() with default separators now matches what requests.post(json=...) puts on the wire. Two issues remain:
1. Verification still re-serializes (cross-language breakage)
_verify_webhook_signature in client.py still does:
payload_bytes = json.dumps(payload).encode("utf-8")If a JS sender sends {"key":"value"} (no spaces, JSON.stringify output), the Python verifier will re-serialize as {"key": "value"} (with spaces) → signature mismatch. This needs the same raw-body treatment as the JS PR (#314) — accept raw body bytes and use them directly for verification, with json.dumps() as a fallback.
2. Timestamp format doesn't match the spec
The test uses an ISO 8601 timestamp:
header_timestamp = "2025-01-15T10:00:00Z"But the spec (and the JS client) uses Unix seconds:
X-ADCP-Timestamp: <unix timestamp in seconds>
The signing function get_adcp_signed_headers_for_webhook should be checked too — if it's generating ISO timestamps in the header, every JS receiver will reject it because parseInt("2025-01-15T10:00:00Z", 10) returns 2025, which will fail the 5-minute window check.
Summary
- ✅ Signing serialization fix is correct
- ❌ Verification needs raw body support
- ❌ Timestamp format needs to be Unix seconds, not ISO 8601
Addresses PR review feedback: - _verify_webhook_signature now accepts optional raw_body param to avoid re-serializing the payload, preventing cross-language JSON mismatches - Timestamp format changed from ISO 8601 to Unix seconds to match the AdCP spec and JS reference implementation - Added test for raw_body signature verification path Fixes #151
|
Both issues addressed in 4a663e5: 1. Verification raw body support — 2. Unix timestamps — |
Add TestHMACTestVectors class with parametrized tests against all 10 official test vectors from adcontextprotocol/adcp PR #1383. Validates signing, verification via raw_body, and rejection of tampered signatures.
Summary
get_adcp_signed_headers_for_webhook()was computing HMAC over compact JSON (separators=(",", ":")) but callers send the body viajson=kwarg which uses default separators (with spaces), causing signature mismatches on receivers. Changed tojson.dumps()with default separators, aligning with the JS reference implementation'sJSON.stringify()behavior._verify_webhook_signatureandhandle_webhooknow accept an optionalraw_bodyparameter. When provided, the raw HTTP body bytes are used directly for HMAC verification instead of re-serializing, preventing cross-language JSON serialization mismatches (e.g. JS compact vs Python spaced).X-AdCP-Timestampfrom ISO 8601 to Unix seconds, matching the AdCP spec and JS reference implementation.get_adcp_signed_headers_for_webhooknow acceptsstr | int | Noneand defaults toint(time.time()).TestHMACTestVectorsclass validating all 10 vectors from adcontextprotocol/adcp PR #1383 — signing, verification viaraw_body, and rejection of tampered signatures all pass.Fixes #151
Test plan
raw_bodyverification works with compact, spaced, pretty-printed, and unicode JSON