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):
...
import falcon
class ApiWebhook:
def on_post(self, req: falcon.Request, resp: falcon.Response) -> None:
headers = req.headers_lower
payload = req.body
if is_valid_webhook_event(headers, payload):
...
app.add_route(ROUTE_API_WEBHOOK, ApiWebhook())
from flask import request
@app.route(ROUTE_API_WEBHOOK, methods=["POST"])
def api_webhook():
headers = request.headers
payload = request.get_data()
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.