Approval gate before posting AI video: verify the Sume webhook
Hold every AI video for human review before it posts. A Node verifier for Sume's signed job webhooks that refuses an empty secret, plus the review steps.

Put a human step between a finished Sume job and the post: receive the signed job.completed webhook, verify the signature, and queue the video for review instead of publishing. With platforms tightening labels and spam checks, an approval gate is the cheapest control you have.
Sume signs every delivery with an HMAC over <timestamp>.<raw_body> and sends it in x-sume-webhook-signature: sume-v1=<hex>, with x-sume-webhook-timestamp beside it, as described on the webhooks page.
What the webhook gives you
Only terminal events are sent.
| Item | Value |
|---|---|
| Events | job.completed, job.failed, job.canceled |
| URL | Public HTTPS only; localhost, private networks and non-HTTPS are rejected |
| Signature | sume-v1=<hex>, HMAC-SHA256 of timestamp + "." + raw body |
| Rotation | The header carries one entry per live secret, newest first, comma separated |
| Replay | Reject a timestamp outside your tolerance window |
A verifier that refuses an empty secret
import crypto from "node:crypto";
export function verify({ rawBody, timestamp, signatureHeader, secret, toleranceSeconds = 300 }) {
if (!secret) throw new Error("empty webhook secret");
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return signatureHeader.split(",").some((part) => {
const [scheme, sig] = part.trim().split("=");
if (scheme !== "sume-v1" || !sig || sig.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
});
}Wire it up
Verify against the raw request body, not a parsed and re-serialized one, since any change in whitespace changes the HMAC. Read the signing secret from the dashboard Webhooks tab, or from GET /v1/webhooks/signing-secret with a key that has account:read, and keep it in an environment variable. Compare the secret fingerprint header when a signature fails, as the docs suggest.
The review step
After a valid job.completed, do not post. Store the job id and the media.sume.com URL, and send a reviewer a link. A reviewer should check:
- The video matches the brief and no real person or brand is shown by mistake.
- Every claim on screen has a source on your sheet.
- The AI disclosure is ready: the platform label at upload and any on-screen line.
- Audio and captions are right. A video inspect transcript ($0.01 per audio minute) makes the spoken claims easy to read.
Keep a poll fallback
A webhook can be missed. Jobs and results describes the poll fallback to keep alongside it, so a job that completes while your endpoint is down is still picked up. For a proof-then-final loop with clients, see the 480p proof workflow.
Sources
Related posts
More in Integrations
- Cline autoApprove for Sume: list only the read-only tools
Cline's remote MCP entry supports an autoApprove array. Put Sume's read tools in it and keep paid creates such as generate_image on manual approval.
- Azure DevOps remote MCP is GA: run it beside Sume's hosted MCP
Azure DevOps remote MCP went GA in sprint 278 with Foundry and Copilot Studio support. Here is how to hold it and Sume's MCP with separate credentials.
- BigQuery product table to Sume bulk queues and a queue map
Read active SKUs from BigQuery, cast columns to plain JSON types, create one Sume bulk queue per 100 rows, and stream queue id, index and SKU to a map table.
- Claude Code 2.1.290 claude -p skips resources/list: Sume is tools-only
Claude Code 2.1.290 no longer holds the first claude -p turn for HTTP MCP servers to answer resources/list. Sume declares tools only, so nothing is lost.
Written by Sume