Valider la signature d’un webhook

Dans les morceaux de code suivants, une fonction is_valid_webhook_event(headers, payload) est définie : elle accepte les entêtes et le corps de la requête HTTP du webhook.

Examples d’utilisation :

from bottle import request


@app.post(ROUTE_API_WEBHOOK)
def api_webhook():
    headers = request.headers
    payload = request.body.read()
    if is_valid_webhook_event(headers, payload):
        ...

BTCPay Server

import hmac
from hashlib import sha256

WEBHOOK_SECRET = "xxx"


def is_valid_webhook_event(headers: dict[str, str], payload: bytes) -> bool:
    algo, signature = headers["btcpay-sig"].split("=", 1)
    assert algo == "sha256"
    expected_sig = hmac.new(WEBHOOK_SECRET.encode(), payload, sha256).hexdigest()
    return hmac.compare_digest(expected_sig, signature)

PayPal

Requirements: cryptography, requests.

import base64
import zlib

import requests
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

WEBHOOK_ID = "xxx"


def get_certificate(url: str) -> str:
    """Download the PayPal certificate."""
    with requests.get(url, timeout=30) as req:
        return req.text


def is_valid_webhook_event(headers: dict[str, str], payload: bytes) -> bool:
    # Create the validation message
    transmission_id = headers["paypal-transmission-id"]
    timestamp = headers["paypal-transmission-time"]
    crc = zlib.crc32(payload)
    message = f"{transmission_id}|{timestamp}|{WEBHOOK_ID}|{crc}"

    # Decode the base64-encoded signature from the header
    signature = base64.b64decode(headers["paypal-transmission-sig"])

    # Load the certificate and extract the public key
    certificate = get_certificate(headers["paypal-cert-url"])
    cert = x509.load_pem_x509_certificate(certificate.encode(), default_backend())
    public_key = cert.public_key()

    # Validate the message using the signature
    try:
        public_key.verify(signature, message.encode(), padding.PKCS1v15(), hashes.SHA256())
    except InvalidSignature:
        return False
    return True

Stripe

import hmac
from hashlib import sha256

WEBHOOK_SECRET = "xxx"


def is_valid_webhook_event(headers: dict[str, str], payload: bytes) -> bool:
    header = headers["stripe-signature"]
    list_items = [i.split("=", 2) for i in header.split(",")]
    timestamp = int(next(i[1] for i in list_items if i[0] == "t"))
    signatures = [i[1] for i in list_items if i[0] == "v1"]
    signed_payload = f"{timestamp}.{payload.decode()}"
    expected_sig = hmac.new(WEBHOOK_SECRET.encode(), signed_payload.encode(), sha256).hexdigest()
    return any(hmac.compare_digest(expected_sig, sig) for sig in signatures)

📜 Historique

2026-02-14

Premier jet.