Verify a webhook before trusting its event body

A webhook endpoint is public by design. Before parsing its event as trusted provider data, verify the signature over the exact bytes the provider signed. Rebuilding JSON first can change whitespace or key order and invalidate the check.
Compute the expected HMAC from raw request bytes, compare the received signature, then parse the verified event.
Demonstrate a raw-byte HMAC check
This local fixture uses a deliberately invented signature format: a bare hexadecimal HMAC-SHA256 digest. It is not a drop-in implementation for a named provider.
import hashlib
import hmac
import json
def verify(body, signature, secret):
if len(signature) != 64:
return False
try:
int(signature, 16)
except ValueError:
return False
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature.lower())
secret = b"synthetic-test-secret"
body = b'{"id":"event-1","status":"ready"}'
signature = hmac.new(secret, body, hashlib.sha256).hexdigest()
assert verify(body, signature, secret)
assert not verify(body + b" ", signature, secret)
event = json.loads(body)
assert event["id"] == "event-1"
The body must come from the framework's raw request interface before a parser or middleware transforms it. The secret belongs in runtime configuration. Do not print the real secret, complete signature headers or sensitive payloads in routine logs.
A real provider may include a version prefix, timestamp, multiple signatures or a different signed input. Implement its documented format exactly. This fixture demonstrates byte handling and comparison, not provider compatibility.
Verify authenticity and freshness separately
A valid signature can be replayed if the same signed request is accepted again. Follow the provider's timestamp-window rules where provided, and deduplicate events using a durable event identifier. HMAC alone does not establish that an event has not already been processed.
Set a request-body limit before buffering the raw body. Return a bounded response and persist accepted events durably before acknowledging work that must survive a worker outage.
Test a valid fixture, one altered byte, an invalid digest and a request signed with a different secret. If keys rotate, test the permitted overlap period and remove old keys according to the provider's contract.
Also validate the parsed event schema and authorization context after the signature passes. A signed event can still contain unsupported types or refer to an account the application no longer serves.
Postzyo provides related integration context. Python documents HMAC and digest comparison; RFC 2104 defines HMAC.