Zest-Signature header that lets you authenticate the request before processing it. The scheme is HMAC-SHA256 over the timestamped raw body, modelled on Stripe’s signing format.
Signature header format
Future signature versions will append additional
vN=... tokens; partners should match on the version they support and ignore others.
Algorithm
- Read the
Zest-Signatureheader. Parse outtandv1. - Reject if
|now_unix - t| > 300(5 minute replay window). - Compute
expected = hmac_sha256(secret, f"{t}.{raw_body}"). - Compare
v1toexpectedin constant time. Reject on mismatch.
whsec_<hex> string Zest issued you. Zest stores it AES-256-GCM-encrypted at rest and never logs it.
Python
Node.js
Common pitfalls
Computing HMAC over the parsed body, not the raw bytes
Computing HMAC over the parsed body, not the raw bytes
Most JSON middleware mutates whitespace and key order. The signature is computed over the exact bytes Zest sent — verify before any parsing. Use
express.raw() in Node, await request.body() in FastAPI/Starlette.Forgetting the period separator
Forgetting the period separator
The signed payload is
<t>.<body>, not <t><body>. Drop the period and every signature mismatches.String comparison instead of constant-time
String comparison instead of constant-time
== reveals timing-side-channel info. Always use hmac.compare_digest / crypto.timingSafeEqual.No replay-window check
No replay-window check
Without a window check, a captured request can be replayed forever. Reject if
|now - t| > 300s.Rotating secrets
Emailsara@zestholdco.com to rotate. Zest will issue a new whsec_* and accept signatures from both old and new for a short overlap window.