PricingAPI keys
OverviewQuickstartAuthentication
GenerateModelsImagesVideoUploads
JobsJobs and pollingWebhooks
ReferenceErrors and retriesLimitsClient
BillingBilling
Media toolsOverviewAvatarRemix mediaFace swapMusicTimelineVideo captions
MCPOverviewQuickstartOAuthTools and gates
CLIOverviewInstall and loginCommandsAgent skills
API referenceAPI reference
EnterpriseOverviewAgentsFormatsBest practices

Jobs

Webhooks

Instead of polling, pass a URL and Sume POSTs the outcome there once the job ends. Every delivery is signed.

Set a webhook

Add mode: "webhook" and webhook_url to any generate request. The URL must be public HTTPS; localhost, private-network and plain HTTP URLs are refused when you submit.

cURL
curl -X POST https://api.sume.com/v1/video-router/generate \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "prompt": "A paper boat drifting down a rain gutter",
    "duration": 5,
    "mode": "webhook",
    "webhook_url": "https://example.com/hooks/sume"
  }'

Events

eventstatusWhen
job.completedOKpayload.artifacts holds the output.
job.failedERRORerror says why.
job.canceledERRORThe job was canceled.
job.completed
{
  "event": "job.completed",
  "request_id": "job_7513b52d-3669-4ffe-b317-8b02000715eb",
  "job_id": "job_7513b52d-3669-4ffe-b317-8b02000715eb",
  "status": "OK",
  "payload": {
    "artifacts": [
      {
        "id": "artf_UCdnhWThU_Tbemv7E2gE7-ee9lNCIDxP",
        "url": "https://media.sume.com/artifacts/artf_UCdnhWThU_Tbemv7E2gE7-ee9lNCIDxP/images.png",
        "type": "image",
        "content_type": "image/png"
      }
    ]
  }
}

Verify the signature

Each delivery carries two headers:

Headers
x-sume-webhook-timestamp: 1792540000
x-sume-webhook-signature: sume-v1=5f2b…

The signature is HMAC-SHA256, hex, over <timestamp>.<raw body> with your workspace's signing secret. Read the secret in Dashboard → Webhooks or from GET /v1/webhooks/signing-secret. While a secret rotates, the header holds one sume-v1= entry per live secret, comma-separated: accept the delivery if any entry matches. Refuse timestamps more than five minutes old.

import { createHmac, timingSafeEqual } from "node:crypto";

export function isSumeDelivery(rawBody, timestamp, signatureHeader, secret) {
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;
  const digest = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  const expected = Buffer.from(`sume-v1=${digest}`);
  return String(signatureHeader)
    .split(",")
    .map((entry) => Buffer.from(entry.trim()))
    .some((entry) => entry.length === expected.length && timingSafeEqual(entry, expected));
}

Verify the raw body exactly as received, before any JSON parsing.

Deliveries and retries

  • Answer with any 2xx within 10 seconds, after you have stored the event.
  • A delivery without a 2xx is tried up to 10 times: 30 seconds after the first try, then twice as long each time, never more than an hour apart.
  • A delivery can arrive more than once; dedupe on job_id.
  • The job's webhook_delivery field records what was sent. POST /v1/jobs/{id}/webhook/redeliver sends it again.
  • Keep a slow poll as a fallback in case your endpoint was down.

Related

  • Jobs and polling
  • Video
  • Client libraries

On this page

  1. Set a webhook
  2. Events
  3. Verify the signature
  4. Deliveries and retries
  5. Related