One webhook handler for Sume agent, Format and schedule runs
A single receiver can handle all three Sume run types: verify the HMAC over timestamp.body, dedupe on request_id, and branch on outcome (ok, degraded, error).

One endpoint can receive Sume Agent Completions, Format runs and schedule runs. Verify the sume-v1 HMAC-SHA256 signature over <timestamp>.<raw_body>, dedupe on request_id, and branch on outcome rather than status. The event name tells you the run type: agent.run.terminal, format.run.terminal or action.run.terminal.
What arrives
Because the envelope is identical across run types, the receiver does not need three routes. Route on event, load the run by request_id from your own table, and update it. Do the work after you respond, because a slow handler consumes the 10 second attempt window and brings on a retry.
| Field | Meaning |
|---|---|
event | One terminal event per run family |
request_id | Equals the run id and is stable across retries; the dedupe key |
status | OK or ERROR; binary |
outcome | ok, degraded or error; branch on this |
payload | The run receipt, byte-identical to the poll response data; null over 1 MiB |
created_at | When Sume built this delivery; use it to order deliveries |
A receiver that follows the rules
The headers are x-sume-webhook-timestamp and x-sume-webhook-signature. Verify against the raw body before any JSON parse, reject timestamps outside a five-minute window, and compare in constant time. The sample below refuses an empty secret, which is the failure that silently disables a check when an environment variable is missing.
import crypto from "node:crypto";
export function verify(raw, ts, sig, secret, tol = 300) {
if (!secret) return false; // refuse an empty secret
const t = Number(ts);
if (!Number.isFinite(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > tol) return false;
const mac = crypto.createHmac("sha256", secret).update(`${t}.${raw}`).digest("hex");
const a = Buffer.from(sig || "");
const b = Buffer.from(`sume-v1=${mac}`);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
export function route(body, seen) {
if (seen.has(body.request_id)) return "duplicate";
seen.add(body.request_id);
switch (body.outcome) {
case "ok": return `ship ${body.event}`;
case "degraded": return "review artifacts";
default: return "retry or alert";
}
}Why branch on outcome
A run can complete, bill you and produce real media but fail to project it into your output_schema. The envelope then says status: OK with outcome: degraded and output: null, with the reason in output_error. A handler that reads only status cannot tell that case from a clean delivery.
Delivery behavior to plan for
- Return a
2xxquickly and process after responding. Each attempt has a 10 second timeout. - Sume makes up to 10 attempts with exponential backoff, then marks
webhook_delivery.statusasexhausted. Redirects are not followed. - A
payload: nullwithpayload_too_largemeans fetch the receipt fromresult_url; the run itself did not fail. - Canceled and skipped runs deliver nothing.
- To replay a real terminal delivery use
POST /v1/format-runs/{run_id}/webhook/redeliver, which keeps the same secret and fingerprint.
Where the secret comes from
Read your signing secret on the dashboard Webhooks tab or from GET /v1/webhooks/signing-secret with an account:read key, and keep it as SUME_COM_WEBHOOK_SIGNING_SECRET. Compare the x-sume-webhook-secret-fingerprint header with the fingerprint on the dashboard instead of logging the secret. For TypeScript, @sume-com/sdk ships verifyWebhook.
Sources
Related posts
More in Agents
- Polling a Sume agent run: next_action values and backoff
Poll Sume runs by branching on next_action (poll_status, retry_later, none), using status_url and result_url, backing off on 429 and 503.
- Scheduled or Format API: does the clock or your user start the run?
A Sume Scheduled run fires on a cron; a Format run fires when your backend calls it. Same agent, same receipt, new trigger. How to choose without dupes.
- script_run or bulk runs: where to fan out 20 ad variants
script_run fans out three or more Sume tool calls inside one 55-second request; bulk runs queue up to 100 Format runs for hours. Limits and a rule of thumb.
- Three ways to run the Sume video agent from code
Format runs, Scheduled runs and Agent Completions all start the same Sume agent. Pick by how often your task changes, then read the receipt the same way.
Written by Sume