Cloudflare Worker that verifies a Sume video webhook (verifyWebhook)

A 20-line Cloudflare Worker for Sume job webhooks: verifyWebhook from @sume-com/sdk on WebCrypto, empty-secret guard, 204 fast ack. Why the check is async.

4 min readSume
All posts

On Cloudflare Workers, verify a Sume webhook by passing the raw request text, the request headers and your signing secret to verifyWebhook from @sume-com/sdk. It runs on WebCrypto, not node:crypto, so it imports cleanly in a Worker, and it returns true only when the sume-v1 signature matches and the timestamp is within the replay window.

Why a Worker is a good webhook edge

Video jobs finish on their own schedule. A 30-second Seedance 2.5 render or a 4K Gemini Omni Flash 1.1 clip can take minutes, so the callback arrives when nobody is watching. A Worker is always warm at the edge, answers in milliseconds, and can hand the event to a queue, which keeps you inside Sume's 10-second timeout for each delivery attempt.

The SDK's verifier was written for exactly this runtime. Its source notes that it uses WebCrypto so the published package stays importable from Workers, Deno, Bun and bundlers that refuse node: specifiers. That is also why the function is async, and why you must await it.

What the helper checks

You do not have to hand-roll the HMAC. The helper does the work, and it never throws on a malformed delivery. A missing header, a bad timestamp and a wrong signature all become false, so you branch on one value.

verifyWebhook behaviour (Sume SDK docs, read 2026-10-05)
InputResultNote
Matching sume-v1 signature, fresh timestamptrueConstant-time comparison
Timestamp older than 300 sfalseDefault tolerance, set toleranceSeconds to change it
Missing or garbage headerfalseNever throws
Empty secretfalseRefused before any HMAC work
Two signatures during rotationtrue if either matchesNewest first, comma-separated

The Worker

Install the package in your Worker project with npm install @sume-com/sdk, store the secret with wrangler secret put SUME_COM_WEBHOOK_SIGNING_SECRET, and deploy. The explicit empty-secret check is belt and braces, since it turns a missing binding into a loud 500 instead of a silent stream of 401s.

If you also want the Worker to poll, add a scheduled handler that reads GET /v1/videos/{id} for ids still pending after your own deadline. Cloudflare cron triggers make that a few lines, and it closes the gap for deliveries that exhaust their 10 attempts.

import { verifyWebhook } from "@sume-com/sdk";

export default {
  async fetch(request, env) {
    if (request.method !== "POST") return new Response("no", { status: 405 });
    if (!env.SUME_COM_WEBHOOK_SIGNING_SECRET) {
      return new Response("secret not configured", { status: 500 });
    }
    const body = await request.text(); // raw, before any JSON.parse
    const ok = await verifyWebhook({
      body,
      headers: request.headers,
      secret: env.SUME_COM_WEBHOOK_SIGNING_SECRET,
    });
    if (!ok) return new Response("bad signature", { status: 401 });

    const event = JSON.parse(body);
    if (event.event === "job.completed") {
      await env.JOBS.send({ jobId: event.job_id }); // a Queues binding
    } else if (event.event === "job.failed" || event.event === "job.canceled") {
      await env.JOBS.send({ jobId: event.job_id, terminal: event.event });
    }
    return new Response(null, { status: 204 });
  },
};

Details that bite

Read the body with request.text() before anything else. Sume signs the raw bytes, so a parsed and re-serialized body will never verify. Do not call request.json() first, even in a middleware.

Send only the job id downstream. Fetch the artifact later through GET /v1/videos/{id}/content, with your API key held in a separate secret. The webhook is a doorbell and the poll route is the source of truth, so if the queue consumer sees an id it does not know, it can read the job directly.

Dedupe on job_id in the consumer. Sume retries non-2xx responses up to 10 attempts, and a redeliver call re-posts the real terminal event with a fresh timestamp, so repeats are normal.

Why this matters this season

Real-time avatar systems are moving the other direction. Tavus describes Griffin as a full-duplex video-to-video model, and says Griffin-Lite is available only to select trusted testers as a research preview. You cannot call that from a Worker today. Rendered video jobs, which Sume ships now, are the opposite shape: submit, leave, and let a signed callback bring you back, and the Worker above is the whole receiver.

  • Keep the Worker thin. Verify, enqueue, acknowledge.
  • Use the dashboard Send test action to check the secret before a real render.
  • Keep a poll fallback for events that never arrive.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume