Skip to content

Verifying the signature

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.