Formats

Sume Format run lifecycle: statuses, webhooks, and polling

A Sume Format run goes from queued to processing to a terminal status. Learn the outcome from one signed format.run.terminal webhook, by polling, or both.

6 min readSume
All posts

A Sume Format run is asynchronous: the create call answers at once with a receipt, the run moves from queued to processing to a terminal status, and you learn the outcome from one signed format.run.terminal webhook or by polling. Both carry the identical receipt, and production integrations use both: the webhook as the fast path, a read of result_url as the backup.

Everything below is from Sume's Runs and results and Run webhooks docs pages, read on 2026-09-25.

What statuses does a Format run go through?

Every receipt carries status and a next_action that tells you what to do. A Format run emits only three next_action values: poll_status, retry_later, and none.

Run statuses, from Runs and results, read 2026-09-25.
StatusMeaningNext action
queuedAccepted, not started.poll_status
processingThe run is working.poll_status
completedFinished. output, artifacts[], and primary_output_url are populated.none
failedFinished with an error. artifacts[] still carries whatever was made.none
canceledStopped by POST …/cancel.none
skippedNever ran: you sent on_active_run: "skip" and another run was in flight.retry_later

How do I poll a Format run?

Follow the URLs on the receipt instead of building paths. Most integrations read the full receipt at GET /v1/format-runs/{run_id} until status is terminal. status_url is a smaller payload without output or artifacts; result_url returns the full receipt once terminal and 409 run_not_completed before that; events_url is a phase timeline (preparing, running, finalizing), not a log stream.

  • Back off. Long-form video is 15 to 30 minutes of work, so double the gap up to a minute.
  • Treat a 429 or 503 in the loop as transient. The run is still executing and still spending.
  • Use expires_at as your ceiling. A run is force-finalized as failed 90 minutes from created_at, or sooner when it is older than 25 minutes and has been silent for 10.
  • If queued lasts, read queue.state: waiting inside the normal pickup window, runtime_unavailable past it, with retry_after_seconds to back off.
  • In TypeScript, subscribeFormatRun creates a run and runs this loop; waitForRun does it for a run id you already have.
SLEEP=5
while :; do
  RUN=$(curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID" \
    -H "Authorization: Bearer $SUME_API_KEY")
  STATUS=$(echo "$RUN" | jq -r '.data.status')
  case "$STATUS" in
    queued|processing) sleep "$SLEEP"; SLEEP=$(( SLEEP < 60 ? SLEEP * 2 : 60 )) ;;
    *) break ;;
  esac
done
echo "$RUN" | jq '{status: .data.status, primary: .data.primary_output_url, error: .data.error}'

How do Format run webhooks work?

Send communication.webhook_url, a public HTTPS URL, when you create the run. When the run completes or fails, Sume sends one signed POST carrying the same receipt the poll endpoints return. It fires once per run, however many clips the run made. The full contract is on Run webhooks.

Webhook envelope, from Run webhooks, read 2026-09-25.
FieldWhat to do with it
eventAlways format.run.terminal for a Format run. Route on it without inspecting the body.
request_id, run_idEqual, and stable across retries. Dedupe on them.
statusOK when the run completed, ERROR when it failed.
outcomeok, degraded, or error. degraded means completed and billed with real media, but output is null; see structured output.
created_atWhen this delivery body was built. Order deliveries by it.
payloadThe run receipt, byte-identical to data from GET /v1/format-runs/{run_id}. null only when the receipt is over 1 MiB; then fetch error.result_url.

How do I verify a Sume webhook signature?

Each delivery carries x-sume-webhook-timestamp, x-sume-webhook-signature (sume-v1=<hex hmac-sha256>), and x-sume-webhook-secret-fingerprint. The signature is HMAC-SHA256 over <timestamp>.<raw_body> with your workspace's signing secret, which you read on the dashboard's Webhooks tab or from GET /v1/webhooks/signing-secret with a key that has account:read. The same verifier covers generation-job webhooks; see signed webhooks for video runs.

  • Verify the raw bytes before parsing JSON.
  • Reject timestamps outside a five-minute window.
  • Compare the fingerprint header with the one shown next to the secret.
  • Answer any 2xx within 10 seconds: record the event durably, answer, then do the work.
import { verifyWebhook } from "@sume-com/sdk";

const ok = await verifyWebhook({
  body: rawBody,
  headers: request.headers,
  secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET!,
});

What happens if my webhook endpoint is down?

The run is unaffected: a delivery outcome never changes it. Sume retries up to 10 attempts, backing off by the longer of exponential (30 s × 2^(attempt−1), with jitter) and your Retry-After on a 429 or 503, capped at one hour. Redirects are not followed, so a 3xx counts as a failed attempt.

The receipt's webhook_delivery.status shows where delivery stands: not_armed, pending, retrying, delivered, failed, or exhausted. After you fix your receiver, POST /v1/format-runs/{run_id}/webhook/redeliver (scope formats:write, empty body) re-sends the current receipt with a fresh timestamp and signature, and does not consume one of the ten automatic attempts.

When does a run not send a webhook?

  • When it is canceled. Cancel answers you directly, and cancel_effect says whether the call stopped the run (canceled) or it had already finished (no_op). Generation completed before the cancel is billed.
  • When it is skipped. A skipped run is already terminal on the create response.
  • Again, after it is continued. Continuing with previous_run_id starts a new run with its own single webhook; the original run's webhook already fired and does not fire again.

Sources

Related posts

Written by Sume