Cloudflare Workers CPU time vs wall clock for a Sume webhook
A Sume webhook verifier on Workers spends CPU only on the HMAC and body read, not on waiting. See the limits and a WebCrypto verifier.

A Cloudflare Worker that receives a Sume webhook is billed against CPU time, not against how long the video took to render, and verifying the signature is a small amount of CPU. The Workers limits page lists CPU time as 10 ms on the Free plan and 5 minutes by default on Paid, and says time spent waiting on fetch, KV or a database does not count as CPU time. So the receiver only needs to read the body, check an HMAC and answer; the slow work belongs elsewhere.
We did not measure the verifier on Workers, so treat the 10 ms figure as a limit to test against, not a promise. The same page says an HTTP request has no enforced wall-clock limit while the client stays connected. The receiving request should still be over in milliseconds. Run wrangler tail on a real delivery and read the CPU time it reports before you rely on the Free plan.
Limits that matter for a receiver
Sume job webhooks arrive as a POST with x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>, signed with HMAC-SHA256 over <timestamp>.<raw_body>. Sume waits up to 10 seconds per attempt and tries up to 10 times, so answer quickly with a 2xx after storing the event. The table collects the Cloudflare numbers that bear on that design.
| Limit | Value | Why it matters |
|---|---|---|
| CPU time, Free | 10 ms | Keep verification and storage minimal |
| CPU time, Paid | 5 min default | Not a constraint for an HMAC |
| Waiting on fetch, KV, DB | Not counted as CPU | Storing the event is cheap in CPU terms |
| ctx.waitUntil after response | Up to 30 s | Short follow-up work only; use a Queue for more |
| Subrequests, Free / Paid | 50 / 10,000 | One verified event needs few |
Steps
- Store the signing secret as a Worker secret named
SUME_COM_WEBHOOK_SIGNING_SECRET; return an error and do nothing when it is missing. - Read the body once with
request.text(), then verify with WebCrypto against the exact string. Do not parse and re-stringify. - Reject a timestamp more than 300 seconds from now, which is the replay tolerance the Sume docs use.
- Write the event to a Queue or storage and return 204. Dedupe on
job_id; a retry sends the same job. - Keep polling
GET /v1/jobs/{id}/statusas a backup for events you never received.
Verifier
crypto.subtle.verify compares in constant time, and the loop accepts either entry in a comma separated signature header, which covers the 24 hour dual-signing window after a secret rotation.
const hex = (s) => Uint8Array.from(s.match(/../g) ?? [], (b) => parseInt(b, 16));
export default {
async fetch(request, env) {
if (!env.SUME_COM_WEBHOOK_SIGNING_SECRET) return new Response("no secret", { status: 500 });
const raw = await request.text();
const ts = request.headers.get("x-sume-webhook-timestamp") ?? "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return new Response("stale", { status: 401 });
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(env.SUME_COM_WEBHOOK_SIGNING_SECRET),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
const sigs = (request.headers.get("x-sume-webhook-signature") ?? "").split(",");
for (const part of sigs) {
const sig = part.trim().replace(/^sume-v1=/, "");
if (await crypto.subtle.verify("HMAC", key, hex(sig), new TextEncoder().encode(`${ts}.${raw}`))) {
return new Response(null, { status: 204 });
}
}
return new Response("bad signature", { status: 401 });
},
};
// quick check in Node 18+: sign with the same secret, call worker.fetch(new Request(...), env)What Sume does not do
Sume does not know which plan your Worker is on and does not adapt deliveries to it. If your handler runs over its CPU budget, the delivery fails and Sume retries on its fixed schedule. The SDK's `verifyWebhook` does the same work if you would rather import it than hand-roll the check.
Sources
Related posts
More in Developers
- Platform specs ask for bitrate and GOP: what Sume lets you set
YouTube, TikTok and Meta list bitrate, GOP and audio rates. Sume rejects codec and crf fields. Here is what you can control instead: size, fps, audio.
- Continue a 30 s shot: Video frames still as the next first frame
Extract a last-second still from a 30 s Sume clip with Video frames and send it as first_frame for the next shot. Price of the chain at 720p.
- Convert MAI-Transcribe-2 word offsets to Sume video caption words
Turn word timings in milliseconds into the seconds-based words array Sume video-captions accepts. A short Python converter plus the 60 s and 1,200-word limits.
- curl -d to Sume /v1/videos gives 415: add the JSON Content-Type
A POST /v1/videos with a body that is not application/json gets 415 unsupported_media_type, with the content type you sent in details. The curl fix.
Written by Sume