callback_url on Sume /v1/videos: HTTPS only, not OpenRouter events

POST /v1/videos takes callback_url. It must be HTTPS, and Sume sends its own job.completed envelope, not OpenRouter's video.generation events.

4 min readSume
All posts

What does callback_url do on Sume's /v1/videos?

It makes Sume POST to your URL when the job reaches a terminal state. The URL must be HTTPS. The body is Sume's standard job webhook envelope with events job.completed, job.failed or job.canceled, signed with HMAC-SHA256. It is not the video.generation.* envelope that the OpenRouter video API uses, even though the rest of /v1/videos copies OpenRouter's request shape.

That difference breaks ported code. A receiver that switches on OpenRouter's event names would never see a match, and it would quietly drop every delivery. Route on Sume's names and make unknown events loud.

Sume envelope at a glance

Terminal webhook events for Sume video jobs (docs.sume.com, read 2026-10-06)
Eventstatus fieldWhere the data is
job.completedno errorpayload.artifacts, each with a url
job.failedERRORerror object
job.canceledERRORerror object

Build the body and route the event

The first function refuses a non-HTTPS URL before it spends a request. The second raises on an event it does not know, which is the behaviour you want while migrating.

from urllib.parse import urlparse

def video_body(prompt: str, callback_url: str) -> dict:
    if urlparse(callback_url).scheme != "https":
        raise ValueError("callback_url must be https")
    return {
        "model": "wan-3.0", "prompt": prompt, "duration": 10,
        "resolution": "720p", "callback_url": callback_url,
    }

def route(event: dict):
    kind = event["event"]
    if kind == "job.completed":
        return [a["url"] for a in event["payload"]["artifacts"]]
    if kind in ("job.failed", "job.canceled"):
        return event.get("error")
    raise ValueError(f"unexpected event {kind!r}")

print(video_body("A kettle boiling at dawn", "https://example.com/hooks/sume")["callback_url"])
print(route({"event": "job.completed", "job_id": "job_1",
             "payload": {"artifacts": [{"url": "https://media.sume.com/a.mp4"}]}}))
print(route({"event": "job.failed", "status": "ERROR", "error": {"code": "x"}}))

Before you go live

Verify the signature on the raw body before you route, using the x-sume-webhook-timestamp and x-sume-webhook-signature headers and your workspace secret. Deduplicate on job_id, since a delivery can repeat up to ten times. Keep polling polling_url as a fallback for a delivery that never arrives.

The submit response is a 202 with id, polling_url and status, and the states are pending, in_progress, completed, failed and cancelled. Store id the moment you get it, so the callback has something to match. Your test plan is short: send a test delivery to your endpoint from the dashboard or with POST /v1/webhooks/test-deliveries, confirm your verifier accepts it, and note that the route function above raises on webhook.test, so answer test events before routing if you want them to pass quietly.

Failed and canceled deliveries are the ones teams forget. They carry status: "ERROR" and an error object instead of artifacts, so a receiver that reads payload.artifacts unconditionally will raise a KeyError on exactly the jobs that need attention. Branch on the event name first, as route does, and write the error somewhere a person will see it. Check the usage record for what a failed job was charged, and make sure the order that wanted the clip has an owner.

callback_url is the /v1/videos field for this push, and webhook_url is the field on Sume's other generation routes.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume