Inngest wait for event: resume when a Sume video run ends
Start a Sume run in step.run, turn its webhook into an Inngest event with a transform, then step.waitForEvent on the run id with a 2h timeout.

To make an Inngest function wait for a Sume video run, start the run inside step.run() with communication.webhook_url set to an Inngest webhook URL, let that webhook's transform turn Sume's POST into a sume/format.run.terminal event, and call step.waitForEvent() with an if expression that matches async.data.run_id to your run id. It resolves with the event, or with null if the timeout passes first.
Inngest facts come from its step.waitForEvent() reference, Wait for an Event guide, Consuming webhook events, and usage limits pages; Sume facts come from Create a run, Runs and results, and Run webhooks. All were read on 2026-09-27. Sume has no Inngest integration: the function calls Sume's REST API with fetch. The same wait with Trigger.dev waitpoint tokens is in Trigger.dev wait for webhook.
Which waitForEvent options fit a Sume run?
step.waitForEvent(id, options) pauses the function until a matching event arrives or the timeout is reached. For a Format run, set these:
| Option | Value | Why |
|---|---|---|
event | "sume/format.run.terminal" | The name your webhook transform returns. Sume POSTs one format.run.terminal when a Format run completes or fails. |
if | async.data.run_id == "<run id>" | A CEL expression over the trigger (event) and the wait event (async). match compares the same property on both events, and the trigger has no run id. |
timeout | "2h" | A duration string, a number of milliseconds, or a date. Sume force-finalizes a run at most 90 minutes after created_at. |
| Result | The event, or null | null means the timeout passed first. Read the run from the API. |
How do I turn Sume's webhook into an Inngest event?
Create a webhook in the Inngest dashboard under Manage, then Webhooks. It gets a unique URL, which you send as communication.webhook_url. Its transform, a JavaScript function that runs on Inngest's servers, receives the parsed JSON, the headers, the query parameters, and the raw body string, and returns an event with name and data.
Inngest's advice for signed webhooks is to put the raw body and the signature in the event and verify inside your function. Header names arrive canonicalized, so read X-Sume-Webhook-Signature. An id makes Inngest ignore later events with the same id for 24 hours, and it is global across event types, so combine Sume's event and request_id, which is stable across retries.
// Inngest dashboard: Manage > Webhooks > your Sume webhook > Transform
function transform(evt, headers = {}, queryParams = {}, raw = "") {
return {
id: `sume-${evt.event}-${evt.request_id}`, // request_id repeats on Sume retries
name: `sume/${evt.event}`, // for example sume/format.run.terminal
data: {
run_id: evt.run_id,
raw, // the exact body Sume signed
sig: headers["X-Sume-Webhook-Signature"], // canonicalized header names
ts: headers["X-Sume-Webhook-Timestamp"],
},
};
}What does the waiting function look like?
Inngest runs each step as a separate HTTP request and wants non-deterministic work, such as API calls, inside step.run(). So the create call is a step, and a retried step sends the same Idempotency-Key and body, which Sume answers with the original run and no second charge. The signature check is a step too, because it reads the clock: verifyWebhook from @sume-com/sdk accepts any sume-v1= entry and applies a 300-second replay window by default. If no event came, or it does not verify, the function reads GET /v1/format-runs/{run_id} with its key. The code uses Inngest's TypeScript SDK v4 syntax.
import { verifyWebhook } from "@sume-com/sdk";
import { inngest } from "./client";
const API = "https://api.sume.com/v1";
const auth = { Authorization: `Bearer ${process.env.SUME_API_KEY}` };
export const productVideo = inngest.createFunction(
{ id: "product-video", triggers: { event: "shop/video.requested" } },
async ({ event, step }) => {
const runId = await step.run("start-run", async () => {
const res = await fetch(`${API}/formats/acme/product-video/runs`, { method: "POST",
headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": `video-${event.data.orderId}` },
body: JSON.stringify({ input: event.data, communication: { webhook_url: process.env.INNGEST_SUME_WEBHOOK_URL } }) });
return (await res.json()).data.id as string;
});
const done = await step.waitForEvent("wait-for-run", {
event: "sume/format.run.terminal", timeout: "2h", if: `async.data.run_id == "${runId}"` });
return step.run("read-receipt", async () => {
const signed = done !== null && (await verifyWebhook({ body: done.data.raw,
headers: { "x-sume-webhook-signature": done.data.sig, "x-sume-webhook-timestamp": done.data.ts },
secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET! }));
const receipt = signed ? JSON.parse(done.data.raw).payload : null; // null over 1 MiB
return receipt ?? (await (await fetch(`${API}/format-runs/${runId}`, { headers: auth })).json()).data;
});
},
);What if the run finishes before the wait starts?
Inngest's guide is explicit: a wait listens for events from the moment its code runs, so an event sent before step.waitForEvent() executes is not matched. A run that ends before the wait registers is missed, and the function then learns the result only at the timeout, from the fallback read. Canceled and skipped runs never send a webhook at all, so the same read covers them; check status on the receipt it returns. If you shorten the timeout, it still never cancels the run: the run keeps running and billing.
What limits apply?
Plan for these three:
- Event size. Inngest caps a single event at 256 KiB on Free, 512 KiB on Basic, and 3 MiB on Pro, and the raw body travels inside the event. Sume inlines run receipts up to 1 MiB, so on a smaller plan a large receipt may not fit; the fallback read still gets it, but only once the wait times out.
- Transform errors. A transform that throws makes Inngest answer
400. Sume treats that as a failed attempt and retries, up to 10 attempts. - Keys. Keep
SUME_API_KEYand the signing secret in your app's server-side environment, never in frontend code; Sume's docs say keys belong on trusted servers. See Signed webhooks for Sume video runs for rotation and redelivery.
Sources
- Create a run
- Runs and results
- Run webhooks
- Webhooks
- Verifying webhooks
- Format cookbook
- Authentication
- Inngest: step.waitForEvent() reference (read 2026-09-27)
- Inngest: Wait for an Event (read 2026-09-27)
- Inngest: Consuming webhook events (read 2026-09-27)
- Inngest: Sending events (read 2026-09-27)
- Inngest: Usage limits (read 2026-09-27)
- Inngest: How functions are executed (read 2026-09-27)
- Inngest: inngest.createFunction() reference (read 2026-09-27)
- Inngest: step.run() reference (read 2026-09-27)
Related posts
More in Integrations
- LangChain video generation tool that runs a Sume Format
A LangChain @tool can start a Sume Format run, cap its spend, key it for safe retries, and return a run id the agent checks until the video is ready.
- LlamaIndex image generation tool with the Sume Image API
Wrap POST /v1/images in a LlamaIndex FunctionTool: send sume/auto and a prompt, return URLs on a 200, and hand back the job id on a 202.
- Make.com AI video scenario with Sume: HTTP and a webhook
Split a Make.com AI video scenario in two: Make a request starts a Sume run; a custom webhook takes the signed result and checks it with sha256().
- Mastra MCP client: connect an agent to Sume's hosted MCP
Connect a Mastra agent to Sume's hosted MCP server with MCPClient: an API-key header in requestInit, a tool allow-list, and approval for paid calls.
Written by Sume