Forward a finished Sume run to team chat with a signed webhook
A standard-library Python receiver that verifies the sume-v1 signature, rejects an empty secret, and forwards primary_output_url when a Format run ends.

To post finished Sume videos into team chat, run a small HTTPS receiver that verifies the signature on each format.run.terminal delivery and then forwards primary_output_url. The signature is HMAC-SHA256 over the timestamp, a dot, and the raw body, sent as sume-v1=<hex>. The receiver below rejects a request when the secret is empty.
import hashlib, hmac, json, os, time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "").encode()
def verify(raw, ts, header):
if not SECRET or not ts or not header:
return False
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
mac = hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256)
want = "sume-v1=" + mac.hexdigest()
return any(hmac.compare_digest(p.strip(), want) for p in header.split(","))
class Hook(BaseHTTPRequestHandler):
def do_POST(self):
raw = self.rfile.read(int(self.headers.get("Content-Length", "0")))
sig = self.headers.get("x-sume-webhook-signature")
ts = self.headers.get("x-sume-webhook-timestamp")
ok = verify(raw, ts, sig)
if ok:
run = json.loads(raw)["payload"]
print(run["id"], run["status"], run.get("primary_output_url"))
self.send_response(200 if ok else 401)
self.end_headers()
HTTPServer(("0.0.0.0", 8080), Hook).serve_forever()What the code does
It reads the raw bytes first, because the signature covers the exact body Sume sent. It returns 401 for a missing header, a stale timestamp or an empty secret. The header can carry more than one sume-v1= entry during a secret rotation, so the check accepts any match.
| Property | Value |
|---|---|
| Signed over | <timestamp>.<raw_body>, HMAC-SHA256 |
| Replay window | Reject timestamps outside about five minutes |
| Success | Any 2xx within 10 seconds |
| Retries | Up to 10 attempts, backoff to a maximum of one hour |
| Redirects | Not followed; a 3xx is a failed attempt |
| Dedupe key | request_id, which repeats on retries |
From print to chat
Replace the print with a call to your chat tool's incoming-message URL, using the id, the status and primary_output_url. Do the slow work after you answer: write the event to durable storage, return 200, then post. Sume waits only 10 seconds for your answer, and a slow handler turns into a retry and a duplicate message.
Handle failures and duplicates
- A failed run still delivers, with
statusfailed and anerror;artifacts[]lists any media it made. - Canceled and skipped runs never deliver, so poll for those if you cancel from your side.
- Deduplicate on
request_id, since each retry repeats it. - Register a public HTTPS URL. Localhost, private ranges and plain HTTP are rejected at create.
Where to get the secret
Read the signing secret on the Webhooks tab of the dashboard, or from GET /v1/webhooks/signing-secret with a key that has account:read. Store it as SUME_COM_WEBHOOK_SIGNING_SECRET. Job webhooks and run webhooks share it, so one verifier covers both.
Sources
Related posts
More in Integrations
- GitHub Actions weekly Sume bulk queue that fails on counts.failed
A scheduled workflow that creates a Sume bulk queue, polls it through transient 429 and 503, and turns a completed queue with failed items into a red job.
- Sume hosted MCP: API key or OAuth for an overnight agent job?
OAuth fits a person at a keyboard; an API key fits an unattended agent. How Sume hosted MCP treats each, plus the guardrails to set before you leave it running.
- Let an agent pick talking video, Fabric or H3 Max over MCP
Rules for an agent on hosted MCP: avatar-videos_create for scripts, avatar-image-to-video_create for audio, and dry_run plus max_spend_usd before any paid call.
- n8n 2.43.0: resume agents after waiting workflows, with Sume runs
n8n 2.43.0 (pre-release, Oct 6) resumes Preview agents after waiting workflows finish. Pair a long Sume run with one webhook, deduped on request_id.
Written by Sume