Webhook signature mismatch: Sume fingerprint vs OpenRouter t=,v1=
A video webhook that fails verification has three usual causes: parsed body, wrong secret, wrong header format. Sume adds a secret fingerprint header.

When a video webhook fails verification, check the body, then the secret, then the header format. Sume helps with the second one: each delivery carries x-sume-webhook-secret-fingerprint, which you compare with the fingerprint shown beside your secret in the dashboard. You never paste the secret itself into a ticket. A client written against OpenRouter needs the third check, because the two services format the signature differently.
Two signature formats
The OpenRouter video guide, read today, says the signature header is X-OpenRouter-Signature: t=<timestamp>,v1=<hash>, an HMAC-SHA256 over the raw body. It also sends an idempotency header, X-OpenRouter-Idempotency-Key, built from the job id and status.
Sume signs <timestamp>.<raw_body>, sends the timestamp in x-sume-webhook-timestamp, and puts sume-v1=<hex> in x-sume-webhook-signature. The video docs say Sume sends its own job envelope, not the OpenRouter video.generation.* events.
| Item | OpenRouter | Sume |
|---|---|---|
| Signature header | X-OpenRouter-Signature | x-sume-webhook-signature |
| Format | t=<timestamp>,v1=<hash> | sume-v1=<hex> |
| Signed data | Raw body | <timestamp>.<raw_body> |
| Timestamp | Inside the header | x-sume-webhook-timestamp |
| Event names | video.generation.* | job.completed, job.failed, job.canceled |
Three checks in order
Do these in order; the first one that fails is the cause.
- Raw body. Any JSON parse and re-serialize changes the bytes. In Next.js use
await request.text(). - Secret. Compare the fingerprint header with the dashboard value. A rotation changes the fingerprint to the new secret at once.
- Header format. A Sume header can carry two entries during the 24 hour rotation window,
sume-v1=<new>,sume-v1=<old>. Accept the delivery when any entry matches.
What a failed check should return
Return 401 for a bad signature. Sume retries up to 10 times, so a wrong secret shows up as a run of retries, not one loss. The status value on the delivery row moves from retrying to exhausted; fix the secret, then redeliver the real event.
import { verifyWebhook } from "@sume-com/sdk";
export async function check(req: Request, secret: string) {
if (!secret) return new Response("not configured", { status: 500 });
const body = await req.text();
const ok = await verifyWebhook({ body, headers: req.headers, secret });
if (ok) return null;
const fp = req.headers.get("x-sume-webhook-secret-fingerprint");
console.warn("sume signature failed, fingerprint", fp);
return new Response("bad signature", { status: 401 });
}Reading the fingerprint
The fingerprint is a short identifier of the secret, not the secret. The dashboard shows it next to the value, and each delivery carries it in x-sume-webhook-secret-fingerprint. It also appears on the delivery receipt as signing_secret_fingerprint. If the two differ, your receiver holds a different secret from the one Sume signed with, and no code fix helps.
After a rotation the header names the new secret from the first moment, and also during the 24 hour window. It tells you which secret to move to. It does not list the secrets Sume still accepts.
Porting from the OpenRouter format
If you wrote a verifier for the t=<timestamp>,v1=<hash> header, you split the header, build the signed string and compare. For Sume you read the timestamp from its own header and build <timestamp>.<raw_body> with a literal dot. Then you compare to each sume-v1= entry in a constant-time check. The SDK helper does this, enforces a 300 second replay window first, and returns false for a malformed header instead of throwing.
Sources
Related posts
More in Developers
- Test a video webhook receiver before the first job: Sume vs fal
Sume sends a signed dummy webhook.test with one API call. fal retries real results up to 31 times and treats 3xx as failure, so test your URL first.
- Which languages? Sume's language fields vs MAI's 23 and 60 counts
Microsoft states 23 languages for MAI-Voice-2.1 and 60 for MAI-Transcribe-2-Streaming. Sume publishes no count; here are its language fields.
- Which Sume API errors to retry and which to stop on: Node wrapper
A retry policy by error.code for Sume submits: retry rate_limited, queue_full, provider_capacity_exceeded; stop on 400, 401, 402, 409. Node wrapper with jitter.
- Which MCP server lets Claude Code or Cursor generate video and images?
MCP servers that let Claude Code and Cursor make video and images: Sume, fal, Replicate, Runway, Higgsfield. Endpoints, sign-in, billing, setup.
Written by Sume