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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs