Skip to main content

Verifying the signature

Every delivery carries an X-Signature header:

X-Signature: <hex HMAC-SHA256 of the request body, keyed with your short code's secret>

Verify over the RAW body, before parsing

This is the one instruction that cannot be got wrong quietly.

# correct
expected = hmac.new(secret, raw_body_bytes, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, header):
reject()
# WRONG: will fail intermittently and look like our bug
payload = json.loads(raw_body)
expected = hmac.new(secret, json.dumps(payload).encode(), hashlib.sha256).hexdigest()

The signature is computed over the exact bytes we send. Parsing and re-serialising changes those bytes (key order, whitespace, unicode escaping, number formatting), and the digest no longer matches. The failure is intermittent, because it depends on payload content, which makes it very expensive to debug from the wrong end.

Most frameworks give you the raw body, but you often have to ask for it before any JSON middleware consumes the stream.

Two more rules

Compare in constant time. Use your language's hmac.compare_digest or equivalent, not ==.

Reject anything that fails. A delivery with a bad signature is not a delivery to process cautiously: it is one to refuse. Do not fall back to trusting the payload because the signature check was inconvenient.

Your secret

Per short code, minted when the destination is configured, shown once. See Registration.