signature verification

Python

Drop either of these into your Python HTTP framework. Both reproduce the exact canonicalization used by the Freshbatch publisher worker. Requires Python 3.8+, no extra dependencies beyond FastAPI.

FastAPI handler

A complete, ready-to-run endpoint. Replace WEBHOOK_SECRET with your signing key from Dashboard Settings.

import hmac
import hashlib
import json
from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = "replace-with-your-sign-key"

@app.post("/webhook")
async def receive_webhook(
    request: Request,
    webhook_signature: str = Header(..., alias="webhook-signature"),
):
    body = await request.json()
    jobs = body.get("data", [])

    # Canonicalize: sort by url, sort object keys, compact separators
    canonical = json.dumps(
        sorted(jobs, key=lambda job: job["url"]),
        sort_keys=True,
        separators=(",", ":"),
    )

    expected = hmac.new(
        WEBHOOK_SECRET.encode("utf-8"),
        canonical.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    # Constant-time compare — never use ==
    if not hmac.compare_digest(expected, webhook_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Process jobs...
    return {"ok": True, "received": len(jobs)}

Standalone helper

Framework-agnostic function. Pass in the parsed job list, your secret, and the header value. Returns a boolean.

import hmac
import hashlib
import json

def verify_freshbatch_signature(
    jobs: list[dict],
    secret: str,
    received: str,
) -> bool:
    """
    Returns True if the received signature matches.
    Uses constant-time comparison (hmac.compare_digest).
    """
    canonical = json.dumps(
        sorted(jobs, key=lambda j: j["url"]),
        sort_keys=True,
        separators=(",", ":"),
    )
    expected = hmac.new(
        secret.encode("utf-8"),
        canonical.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, received)

See Signature Verification overview for a step-by-step explanation of the canonicalization rules. For a TypeScript version, see TypeScript