OpenRouter video client on Sume: cancelled is terminal, callback_url

Moving an OpenRouter video client to Sume: add cancelled to your terminal statuses, send callback_url on each request, and keep unknown statuses non-terminal.

5 min readSume
All posts

A client written from OpenRouter's video guide treats pending and in_progress as running and completed and failed as final. Sume's /v1/videos has a fifth status, cancelled, so a client that only knows four will poll a cancelled job forever. Add cancelled to the terminal set and treat any status you do not know as not final. For webhooks, send callback_url on each request; Sume's /v1/videos docs describe the per-request field, not workspace defaults.

Statuses and callbacks side by side

OpenRouter's guide, read on 2026-10-09, lists the statuses pending, in_progress, completed and failed, and says webhooks can come from a per-request callback_url or from workspace defaults, are sent on terminal states, and are HMAC-SHA256 signed. Sume's page lists the same four plus cancelled, takes callback_url on the request body, and sends its own job envelope.

Status and webhook differences for a ported client (OpenRouter guide and Sume docs, read 2026-10-09)
TopicOpenRouterSume /v1/videos
Statusespending, in_progress, completed, failedpending, in_progress, completed, failed, cancelled
Webhook targetcallback_url or workspace defaultscallback_url on the request; must be HTTPS
Signature headerX-OpenRouter-Signaturex-sume-webhook-signature (sume-v1=...)
Event shapevideo.generation.* eventsSume job envelope: job.completed, job.failed, job.canceled
Job routesPolling URL onlyAlso GET /v1/jobs/{id}/status and /result

The helper

withCallback validates the URL before the request leaves your machine: it must parse, use https, and not be localhost, 127.0.0.1 or [::1]. Sume rejects such URLs too, but a local check gives a clear message without a round trip. submit posts the body with the Idempotency-Key and expects a 202.

isTerminal holds the three final statuses and returns false for anything else. That is deliberate: if Sume or a later API version adds a status, your loop keeps polling and your deadline ends it, instead of treating an unknown word as success.

export function withCallback(body, url = process.env.SUME_CALLBACK_URL) {
  if (!url) throw new Error("set SUME_CALLBACK_URL: Sume takes the callback per request");
  const u = new URL(url);
  const local = ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname);
  if (u.protocol !== "https:" || local) throw new Error(`callback_url must be public HTTPS, got ${url}`);
  return { ...body, callback_url: url };
}

export async function submit(body, key) {
  const res = await fetch("https://api.sume.com/v1/videos", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUME_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": key,
    },
    body: JSON.stringify(withCallback(body)),
  });
  if (res.status !== 202) throw new Error(`submit ${res.status}: ${await res.text()}`);
  return res.json();
}

const TERMINAL = new Set(["completed", "failed", "cancelled"]);
export const isTerminal = (status) => TERMINAL.has(status); // unknown spellings stay non-terminal

if (process.argv[2] === "demo") {
  console.log(withCallback({ model: "wan-3.0", prompt: "x" }, "https://hooks.example.com/sume"));
  console.log(isTerminal("expired"), isTerminal("cancelled"));
}

Two spellings to watch

The two job vocabularies in Sume do not match. /v1/videos uses cancelled with two l letters, while the generic job routes use queued, processing, completed, failed and canceled with one l. If your code reads both, normalize the word at the edge.

Webhooks are an optimization. The Sume docs say delivery is never the only recovery path, so keep a slow poll on the polling_url for the events that never arrive, as with any callback.

A last check is cheap to run. Cancel a queued job from a test workspace, then poll it with your client. A client that knows cancelled stops and reports it; a client that only knows completed and failed times out at its deadline. That test finds the bug in a minute and costs nothing. Document the two spellings in your client's types, so a later reader does not add canceled to a list that already has cancelled.

  • Replace the OpenRouter signature check with the Sume one before you switch webhooks.
  • Run the demo: node file.mjs demo prints the callback body and two terminal checks.
  • Never put localhost in callback_url, even in tests.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume