Flask webhook receiver for Sume: verify sume-v1, refuse empty secret

A Flask route that verifies the Sume signature on the raw body, takes either rotation entry, checks the replay window, and will not boot without a secret.

5 min readSume
All posts

To receive Sume job webhooks in Flask, read the raw request body, recompute HMAC-SHA256 over <timestamp>.<raw_body> with your workspace signing secret, and compare it to the sume-v1= entries in x-sume-webhook-signature. Refuse to start when the secret is empty; an empty secret makes every signature forgeable.

The route

Use request.get_data() before anything parses JSON, because a re-serialized body does not verify. During a rotation the header carries one entry per live secret, comma-separated, so accept any match. Reject timestamps outside five minutes.

import hashlib, hmac, os, time
from flask import Flask, request

SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
if not SECRET:
    raise SystemExit("SUME_COM_WEBHOOK_SIGNING_SECRET is empty")
app = Flask(__name__)

def verify(raw: bytes, ts: str, header: str, tol: int = 300) -> bool:
    try:
        t = int(ts)
    except ValueError:
        return False
    if abs(time.time() - t) > tol:
        return False
    digest = hmac.new(SECRET.encode(), f"{t}.".encode() + raw, hashlib.sha256)
    want = "sume-v1=" + digest.hexdigest()
    return any(hmac.compare_digest(e.strip(), want) for e in header.split(","))

@app.post("/hooks/sume")
def hook():
    raw = request.get_data()
    h = request.headers
    if not verify(raw, h.get("x-sume-webhook-timestamp", ""),
                  h.get("x-sume-webhook-signature", "")):
        return "bad signature", 401
    return "", 204  # store the event first in real code

After the check

Return any 2xx after durably storing the event. Failed attempts are retried, up to 10 in total at a fixed delay, with a 10 second timeout per attempt, so do the real work after you answer. Use job_id as your idempotency key, and keep status polling as a backup for deliveries that never arrive.

Where the secret comes from

Read it on the dashboard Webhooks tab or from GET /v1/webhooks/signing-secret with a key carrying account:read. If a signature will not verify, compare the fingerprint in x-sume-webhook-secret-fingerprint with the one in the dashboard.

Sume webhook delivery rules (read 2026-10-03)
ItemValue
Signed stringtimestamp, a dot, then the raw body
Signature headerx-sume-webhook-signature: sume-v1=hex
Replay windowFive minutes suggested
AttemptsUp to 10, 10 second timeout each

Sources

Related posts

More in Developers

All Developers posts

Written by Sume