Mercura signs every delivery with HMAC-SHA256 over <Mercura-Timestamp>.<raw_body>, keyed by your subscription secret. Always verify before trusting the payload — and verify against the raw bytes you received, not against a re-serialised JSON object.
import hashlib
import hmac
import time
MAX_TIMESTAMP_SKEW_SECONDS = 5 * 60 # 5 minutes
def verify_mercura_webhook(
*,
secret: str,
raw_body: bytes,
timestamp_header: str,
signature_header: str,
) -> bool:
# 1. Reject stale or future-dated deliveries — protects against replays.
try:
ts = int(timestamp_header)
except (TypeError, ValueError):
return False
if abs(time.time() - ts) > MAX_TIMESTAMP_SKEW_SECONDS:
return False
# 2. Recompute the signature.
signed_payload = f"{ts}.".encode() + raw_body
expected = hmac.new(
secret.encode("utf-8"),
signed_payload,
hashlib.sha256,
).hexdigest()
# 3. Constant-time compare against the value Mercura sent.
prefix = "sha256="
if not signature_header.startswith(prefix):
return False
return hmac.compare_digest(expected, signature_header[len(prefix):])If verification fails, return 400 and log the Mercura-Delivery-Id — Mercura will not retry on a 4xx, which is the right behaviour for a malformed or unauthenticated request.