Verify a Sume Format webhook in Python with an empty-secret guard

Verify x-sume-webhook-signature in Python: HMAC-SHA256 over timestamp.raw_body, a five-minute window, constant-time compare, empty secret refused.

5 min readSume
All posts

Sume signs each terminal webhook with HMAC-SHA256 over <timestamp>.<raw_body>. The signature header looks like sume-v1=<hex>, the timestamp is Unix seconds in x-sume-webhook-timestamp, and you should reject anything outside a five-minute window. Verify against the raw bytes, not a re-serialized JSON object, and refuse to run at all if the secret is empty: an empty secret makes every signature trivially forgeable.

Verifier

import hashlib, hmac, time

def verify(secret: str, timestamp: str, signature: str, raw_body: bytes) -> bool:
    if not secret:
        raise ValueError("webhook secret is empty")
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    if abs(time.time() - ts) > 300:
        return False
    msg = timestamp.encode() + b"." + raw_body
    expected = "sume-v1=" + hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Headers and dedupe

Webhook headers per Sume docs (read 2026-10-03)
HeaderUse
x-sume-webhook-timestampUnix seconds, part of the signed string
x-sume-webhook-signaturesume-v1=<hex digest>
x-sume-webhook-secret-fingerprintIdentify which secret signed it

Handle repeats

Deliveries retry, so dedupe on request_id or run_id before you act. Return a 2xx quickly and do the work after. The TypeScript SDK ships a verifyWebhook helper with the same rules; see SDK webhooks.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume