Trigger.dev wait for webhook: waitpoint tokens for Sume runs

Create a Trigger.dev waitpoint token, send token.url as a Sume run's webhook_url, and wait.forToken() returns when Sume POSTs the run's result.

5 min readSume
All posts

To make a Trigger.dev task wait for a Sume webhook, create a waitpoint token with wait.createToken(), send token.url as the run's communication.webhook_url, and call wait.forToken(). When the run completes or fails, Sume POSTs its receipt to that URL once, and the JSON body becomes the token's output.

Sume has no Trigger.dev integration; the task calls the API with fetch. Sume facts come from Create a run, Runs and results, and Run webhooks; Trigger.dev behavior comes from its docs, read 2026-09-27. The webhook contract is covered in Sume Format run lifecycle.

How do waitpoint tokens map to a Sume run?

Each token rule has a consequence for the run you start.

From Trigger.dev's Wait for token and Wait docs and Sume's Runs and results, read 2026-09-27.
Trigger.devSume side
wait.createToken({ timeout }) returns a token with id and url. timeout defaults to "10m".Set it past the 90-minute bound on a run.
A POST to token.url completes the token; its JSON body becomes the output.Sume POSTs the terminal receipt once, when the run completes or fails.
wait.forToken() returns { ok, output, error }, and the only possible error is a timeout.A canceled or skipped run never POSTs, so its token times out.
Reusing an idempotencyKey on createToken returns the cached token (isCached: true).A retry gets the same token.url, so the Sume request body stays the same.
Trigger.dev Cloud pauses a task that waits longer than a few seconds.A video run takes minutes.

What does the task look like?

Store the key as a Trigger.dev environment variable, marked Secret when you create it, and read it from process.env. The task creates the token, starts a catalog Format run with the token's URL, waits, then reads the run with its own key:

import { task, wait } from "@trigger.dev/sdk";
export const productVideo = task({
  id: "product-video",
  run: async (payload: { orderId: string; photoUrl: string }) => {
    const auth = { Authorization: `Bearer ${process.env.SUME_API_KEY}` };
    const token = await wait.createToken({
      timeout: "2h", idempotencyKeyTTL: "3h",
      idempotencyKey: `sume-${payload.orderId}-v1`,
    });
    const res = await fetch("https://api.sume.com/v1/formats/sume/sume-product-commercial/runs", {
      method: "POST",
      headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": `order-${payload.orderId}-v1` },
      body: JSON.stringify({
        instruction: "Make a vertical product commercial from the attached photo.",
        attachments: [{ type: "input_image", image_url: payload.photoUrl }],
        communication: { webhook_url: token.url },
      }),
    });
    const created = await res.json();
    if (!res.ok) throw new Error(`Sume ${res.status} ${created.error?.code}`);
    const result = await wait.forToken(token); // ok is false only on a timeout
    const run = await fetch(`https://api.sume.com/v1/format-runs/${created.data.id}`, { headers: auth });
    return { delivered: result.ok, run: (await run.json()).data };
  },
});

How long should the token wait?

Longer than the default 10 minutes: long-form host video typically finishes in 15 to 30 minutes. Sume force-finalizes a run as failed 90 minutes after created_at, so a two-hour timeout covers every run that ends on its own. Your timeout does not cancel the run; it keeps running and billing, which is why the task reads the run after a timeout too.

Can the task trust the token's output?

Not on its own. The output is the POST's JSON body, while Sume's HMAC signature travels in the x-sume-webhook-signature header, so the task cannot check it from the output. Treat the token as the signal to look, and read GET /v1/format-runs/{run_id} with your key, as above; its data is the same receipt the webhook carried. The read also covers a receipt over 1 MiB, which arrives with payload: null.

How do retries avoid a second paid run?

Trigger.dev retries a task when it throws, and a retry creates the token and the run again. Two idempotency keys and one habit make that safe:

  • The Sume Idempotency-Key: the same key and body returns 200 with the original run and no second charge.
  • The token's idempotencyKey: a retry gets the cached token and its URL, so the Sume body really is the same. In current code the webhook URL is part of the body the Sume key is checked against, so a new URL with the same key gets 409 idempotency_conflict. idempotencyKeyTTL defaults to 1 hour, so set it past the whole wait.
  • Check result.ok instead of calling .unwrap(), which throws on a timeout and sends the task into its retries.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume