Check your Sume webhook secret at boot: 64 hex and a match
A Node startup check for a Sume webhook receiver: the secret must be 64 hex characters, non-empty, and equal to GET /v1/webhooks/signing-secret.

Make the receiver refuse to start unless SUME_COM_WEBHOOK_SIGNING_SECRET is 64 hex characters, and, if you want a stronger check, equals the secret that GET /v1/webhooks/signing-secret returns. A missing or placeholder secret is the common cause of 401 bad signature on every delivery, and an empty one is worse: an HMAC with an empty key is trivial to forge. Failing at boot costs seconds. Failing in production costs retried deliveries.
What the endpoint returns
The signing-secret read needs an API key with the account:read scope. The OpenAPI spec describes the data object.
| Field | Meaning |
|---|---|
| scope | workspace:{id} for a team key, user:{id} for a personal key |
| version | Derivation version of the secret |
| fingerprint | Non-reversible fingerprint, equal to x-sume-webhook-secret-fingerprint on deliveries |
| secret | The signing secret, 64 hex characters |
The boot check
Save this as check-secret.mjs and run it before the server starts, for example in a container entrypoint. It exits with 1 for a bad format, 2 for a failed read and 3 for a mismatch. On a mismatch it prints only the current fingerprint, never a secret, which makes it safe for CI logs.
import { timingSafeEqual } from "node:crypto";
const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET ?? "";
if (!/^[0-9a-f]{64}$/i.test(secret)) {
console.error("SUME_COM_WEBHOOK_SIGNING_SECRET must be 64 hex characters");
process.exit(1);
}
const res = await fetch("https://api.sume.com/v1/webhooks/signing-secret", {
headers: { "x-api-key": process.env.SUME_API_KEY ?? "" }, // needs account:read
});
if (!res.ok) {
console.error("could not read the signing secret:", res.status);
process.exit(2);
}
const { secret: current, fingerprint } = (await res.json()).data;
const a = Buffer.from(secret);
const b = Buffer.from(current);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
console.error(`env secret is not the current one; current fingerprint ${fingerprint}`);
process.exit(3);
}
console.log(`secret ok, fingerprint ${fingerprint}`);Notes
If the check passes and signatures still fail, the usual cause is the body: verify against the raw bytes, not a re-serialized object.
- During a rotation, the fingerprint on deliveries names the new secret. Install the new secret on your receiver before the 24-hour overlap ends.
- Two members of the same team share one verifier, because a team key uses a workspace-scoped secret.
- Keep the account:read key out of the receiver itself. Run this check in the deploy pipeline, and give the running service only the signing secret.
When to run it
Run it at deploy time and again after every rotation. A rotation produces a new secret, and the fingerprint printed on a mismatch tells you whether the environment still holds the old value. Because the old secret stays valid for 24 hours, a mismatch right after a rotation is a reminder to update the environment, not an outage.
The check compares bytes with timingSafeEqual only out of habit: this runs once, outside the request path, so timing is not a real concern here. The request path is another matter, and verifyWebhook already does a constant-time comparison for each delivery.
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 key scope each Sume webhook endpoint needs
Reading the signing secret needs account:read, rotating and Send test need account:write, and redelivering a job needs jobs:write. A least-privilege map.
- Sume /v1/videos has id and generation_id: which one do you store?
Both fields hold the same job id on Sume, so store id. Why generation_id exists and where that id also works: /v1/jobs status and result, webhooks, redeliver.
- 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.
Written by Sume