Verify Webhook HMAC Signatures in Python
Create and verify HMAC-SHA256 signatures for webhook payloads using Python's hmac module, protecting against tampering.
Python code
22 linesimport hashlib
import hmac
import json
SECRET = b"super-secret-webhook-key"
def create_signature(payload: bytes) -> str:
return hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
def verify_signature(payload: bytes, signature: str) -> bool:
expected = create_signature(payload)
return hmac.compare_digest(expected, signature)
if __name__ == "__main__":
event = {"action": "user.created", "user_id": 42}
payload = json.dumps(event, sort_keys=True).encode()
signature = create_signature(payload)
print(f"Payload: {payload.decode()}")
print(f"Signature: {signature}")
print(f"Valid signature: {verify_signature(payload, signature)}")
print(f"Tampered payload: {verify_signature(payload + b'x', signature)}")
Output
Payload: {"action": "user.created", "user_id": 42}
Signature: 5f6f0d5a4d9f0c8b1e2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f
Valid signature: True
Tampered payload: False
How it works
HMAC signatures ensure that the webhook payload hasn't been altered in transit. hmac.new computes the digest using SHA-256 and the shared secret, producing a fixed-length hexadecimal string. verify_signature recomputes the expected signature and compares it to the received one using hmac.compare_digest, which is constant-time and resistant to timing attacks. The signature changes if any byte of the payload changes, making tampering detectable.
Common mistakes
- Using simple string comparison instead of hmac.compare_digest, which is vulnerable to timing attacks.
- Not canonicalizing the payload (e.g., not using sort_keys=True) so signatures differ across byte orders.
- Using the secret as a plain string instead of bytes, causing type errors.
- Assuming the signature is valid if it is present; always verify both signature and payload freshness.
Variations
- Use `hashlib.sha256` directly with a key by prefixing, but HMAC is the standard for webhooks.
- Sign with the raw bytes of the request body exactly as received, without re-encoding in Python.
Real-world use cases
- Verifying incoming webhook requests from Stripe, GitHub, or Slack to ensure payload integrity.
- Signing outbound webhook notifications to consumers so they can validate authenticity.
- Preventing replay attacks by combining HMAC verification with timestamp checks in your API gateway.
Sponsored
More from API design & gRPC
- Build a Bulk Array POST Mock Server in Python medium
- Build a Mock REST API with PUT and GET in Python medium
- Convert Protobuf to JSON and Dict in Python easy
- Create a Data Helper in Python for gRPC-style APIs easy
- Format data in Python using dataclasses like gRPC messages easy
- Generate an OpenAPI Spec from Mock Routes in Python easy
Keep learning
Related tutorials and quizzes for this topic.