How signing works
Formable computes an HMAC-SHA256 over the exact raw JSON body of the request, using your endpoint’s signing secret as the key, and encodes the result as base64. That value is sent in theContent-Sha256 header.
Your signing secret is provided as a base64-encoded string. Decode it to raw bytes and use those bytes as the HMAC key.
1
Read the raw body
Capture the request body exactly as received, before any JSON parsing or re-serialization. Re-serializing can change whitespace or key order and break verification.
2
Compute the expected signature
HMAC-SHA256 the raw body using the base64-decoded secret as the key, then base64-encode the digest.
3
Compare
Compare your computed value with the
Content-Sha256 header using a constant-time comparison. If they match, the request is authentic.Examples
Common pitfalls
Using the parsed body instead of the raw body
Using the parsed body instead of the raw body
Frameworks that auto-parse JSON discard the original bytes. Re-serializing the parsed object almost always produces a slightly different string (whitespace, key order), so the HMAC won’t match. Always sign over the raw request body.
Forgetting to base64-decode the secret
Forgetting to base64-decode the secret
The secret is a base64 string. Use its decoded bytes as the HMAC key — don’t pass the base64 string directly.
Non-constant-time comparison
Non-constant-time comparison
Compare signatures with a constant-time function (
crypto.timingSafeEqual, hmac.compare_digest) to avoid timing attacks.