PYTHON
Cryptographic Verification of Webhook Signatures with HMAC in Python
Secure your Python application by verifying incoming webhook payloads using HMAC signatures, ensuring data integrity and authenticity.
import hmac
import hashlib
import json
import os
def verify_webhook_signature(payload_raw, signature_header, secret_key, algorithm='sha256'):
"""
Verifies a webhook signature using HMAC.
Args:
payload_raw (bytes): The raw, undecoded payload body received from the webhook.
signature_header (str): The signature string provided in the webhook header (e.g., 'X-Hub-Signature-256').
secret_key (str): Your secret key provided by the webhook sender.
algorithm (str): The hashing algorithm used (e.g., 'sha256').
Returns:
bool: True if the signature is valid, False otherwise.
"""
# Ensure secret_key is bytes
key_bytes = secret_key.encode('utf-8')
# Compute the HMAC digest
hmac_digest = hmac.new(key_bytes, payload_raw, getattr(hashlib, algorithm)).hexdigest()
# The expected signature might be prefixed, e.g., 'sha256=' or 'v1='
# Adjust this parsing based on the specific webhook provider's format
expected_prefix = f"{algorithm}="
if signature_header.startswith(expected_prefix):
provided_signature = signature_header[len(expected_prefix):]
else:
# Assume no prefix if it doesn't match, or raise an error for strictness
provided_signature = signature_header
# Use hmac.compare_digest for constant-time comparison to prevent timing attacks
return hmac.compare_digest(hmac_digest, provided_signature)
# Example usage:
# In a Flask/Django/FastAPI app, payload_raw would be request.get_data()
# secret_key would come from your environment variables.
WEBHOOK_SECRET = os.environ.get('YOUR_WEBHOOK_SECRET', 'a_very_secret_key_from_env_or_config')
# Simulate an incoming payload and signature
sample_payload = {'event': 'user_created', 'data': {'id': 123, 'name': 'John Doe'}}
sample_payload_raw = json.dumps(sample_payload).encode('utf-8')
# Simulate a signature that a webhook provider would send (must be computed with the actual secret)
# For testing, you'd compute it like this:
# computed_test_signature = hmac.new(WEBHOOK_SECRET.encode('utf-8'), sample_payload_raw, hashlib.sha256).hexdigest()
# sample_signature_header = f"sha256={computed_test_signature}"
# Let's use a known correct signature for this example
correct_signature = 'sha256=d3866236b285dd6d75571217e4f9b883023e1074a442168df6d0f622416b772c'
is_valid = verify_webhook_signature(sample_payload_raw, correct_signature, WEBHOOK_SECRET)
print(f"Webhook signature is valid: {is_valid}")
# Test with a bad signature
bad_signature = 'sha256=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'
is_invalid = verify_webhook_signature(sample_payload_raw, bad_signature, WEBHOOK_SECRET)
print(f"Webhook signature with bad signature is valid: {is_invalid}") # Should be False
How it works: This Python snippet provides a function to cryptographically verify webhook signatures using HMAC (Hash-based Message Authentication Code). Webhook providers often send a signature in an HTTP header, which is a hash of the payload signed with a shared secret key. This function recomputes the HMAC digest using your secret key and the raw incoming payload, then securely compares it with the received signature. This process ensures that the webhook payload hasn't been tampered with and truly originates from the expected sender, protecting your application from forged requests and data integrity issues.