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.

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.
| Step | Documented behavior | Source |
|---|---|---|
| Read body | Raw text, before any JSON.parse | Sume docs |
| Verify | Async, returns false instead of throwing | Sume docs |
| Replay window | 300 seconds by default | Sume docs |
| Start Workflow | ctx.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
- Cloudflare Workflow schedules: one Sume Idempotency-Key per tick
A scheduled Cloudflare Workflow that submits a Sume job should derive the Idempotency-Key from the tick, so a replayed step returns the same job.
- Workflows keeps state 7 days now: store your Sume job ids
Cloudflare Workflows on Workers Paid now keeps finished instance state 7 days by default. Save the Sume job id and result URL in your own store.
- Workflows instance limits vs Sume queue headroom: which binds?
Cloudflare lists 50,000 concurrent Workflow instances (Paid, April entry). Sume reports your own generation headroom; size fan-out by that formula.
- Workflows .subscribe() vs Sume job events: push or pull?
Cloudflare Workflows can stream instance events with subscribe(). Sume has no SSE stream for jobs: use a signed webhook, or poll status and the events snapshot.
Written by Sume