Start a Cloudflare Workflow from a Sume webhook with ctx.exports

Verify the Sume webhook signature in a Worker, then start a Workflow with ctx.exports and no workflows binding. Includes a short Worker handler.

4 min readSume
All posts

Read the raw body, check it with verifyWebhook from @sume-com/sdk, then call ctx.exports.MyWorkflow.create({ params }) to start the Workflow. Cloudflare's September 27 entry says a Worker can call a Workflow declared in its exports field this way, without a workflows binding.

Sume facts are from the Verifying webhooks docs; the Cloudflare entry was read 2026-09-30.

What does the handler look like?

It refuses an empty secret, verifies against the raw text, routes on event, and answers fast. The MyWorkflow class is one you export from the same Worker.

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

type Env = { SUME_COM_WEBHOOK_SIGNING_SECRET?: string };
type Ctx = ExecutionContext & { exports: any };

export default {
  async fetch(request: Request, env: Env, ctx: Ctx) {
    const secret = env.SUME_COM_WEBHOOK_SIGNING_SECRET;
    if (!secret) return new Response("no secret", { status: 500 });

    const body = await request.text(); // raw, before JSON.parse
    const ok = await verifyWebhook({ body, headers: request.headers, secret });
    if (!ok) return new Response("bad signature", { status: 401 });

    const event = JSON.parse(body);
    if (event.event !== "job.completed") {
      return new Response(null, { status: 204 }); // unknown or other event
    }
    await ctx.exports.MyWorkflow.create({ params: { jobId: event.job_id } });
    return new Response(null, { status: 204 });
  },
};

What did Cloudflare change?

The changelog example is await ctx.exports.MyWorkflow.create({ params: { name: "World" } }), which returns an instance with an id. It also says a workflows binding and a workflow export with the same name share their instances, and that local use needs Wrangler 4.142.0 or later.

What each side documents, read 2026-09-30. Sume: Verifying webhooks.
StepDocumented behaviorSource
Read bodyRaw text, before any JSON.parseSume docs
VerifyAsync, returns false instead of throwingSume docs
Replay window300 seconds by defaultSume docs
Start Workflowctx.exports.MyWorkflow.create({ params })Cloudflare

Why verify before starting anything?

A Workflow instance costs you work, and an unauthenticated POST to a public route could start one. The check is cheap and runs in Workers because the SDK uses WebCrypto rather than node:crypto.

What if Sume retries the delivery?

Jobs retry up to 10 attempts, and job_id is the idempotency key for job events, so dedupe on it before you create an instance. This post does not claim what Cloudflare does when two creates carry the same parameters; keep your own record of job ids already handled.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume