PYTHON

Secure Webhooks by Verifying Request Signatures

Enhance webhook security by implementing server-side signature verification in Python, ensuring incoming requests are legitimate and haven't been tampered with.

# app.py (Python with Flask)
import hmac
import hashlib
import json
import os
from flask import Flask, request, abort

app = Flask(__name__)

# Load secret key from environment variables for security
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')

@app.route('/webhook', methods=['POST'])
def webhook_receiver():
    if not WEBHOOK_SECRET:
        app.logger.error("WEBHOOK_SECRET environment variable not set.")
        abort(500, description="Server configuration error.")

    # Get the raw request body
    payload = request.data
    
    # Get the signature from the header provided by the webhook sender
    # The header name varies (e.g., 'X-Hub-Signature', 'X-Stripe-Signature', 'X-GitHub-Delivery')
    # For this example, let's assume 'X-Webhook-Signature'
    signature_header = request.headers.get('X-Webhook-Signature')

    if not signature_header:
        app.logger.warning("Missing X-Webhook-Signature header.")
        abort(400, description="Signature header missing.")

    # Calculate the expected signature
    # The hash algorithm (e.g., sha256) and signature format might vary
    # Example: 'sha256=<hex_digest>'
    try:
        method, received_signature = signature_header.split('=')
        if method != 'sha256':
            app.logger.warning(f"Unsupported signature method: {method}")
            abort(400, description="Unsupported signature method.")
    except ValueError:
        app.logger.warning(f"Invalid signature header format: {signature_header}")
        abort(400, description="Invalid signature header format.")

    # Convert secret to bytes
    secret_bytes = WEBHOOK_SECRET.encode('utf-8')

    # Calculate HMAC digest
    computed_hmac = hmac.new(secret_bytes, payload, hashlib.sha256).hexdigest()

    # Compare signatures using a constant-time comparison to prevent timing attacks
    if not hmac.compare_digest(computed_hmac, received_signature):
        app.logger.warning("Webhook signature mismatch.")
        abort(401, description="Invalid signature.")

    # If signature is valid, process the payload
    try:
        data = json.loads(payload)
        app.logger.info(f"Received valid webhook: {data}")
        # Process your webhook data here
        return {"status": "success", "message": "Webhook received and verified."}, 200
    except json.JSONDecodeError:
        app.logger.error("Invalid JSON payload.")
        abort(400, description="Invalid JSON payload.")

if __name__ == '__main__':
    # To run this:
    # 1. pip install Flask
    # 2. Set environment variable: export WEBHOOK_SECRET="your_secure_secret_key"
    #    (This key must match the one configured on the sender's side)
    # 3. python app.py
    # This example runs on http://127.0.0.1:5000/webhook
    app.run(debug=True)
How it works: This Python Flask snippet demonstrates how to secure a webhook endpoint by verifying the incoming request's signature. Many services send a cryptographic signature in a request header, calculated using a shared secret and the request body. This code calculates its own signature from the received payload and compares it with the one provided. Using `hmac.compare_digest` ensures a constant-time comparison, which is crucial for preventing timing attacks. If signatures don't match, the request is rejected, preventing forged or tampered webhooks from processing.

Need help integrating this into your project?

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

Hire DigitalCodeLabs