OpenRouter's workspace default callback URL vs Sume's callback_url

OpenRouter has a workspace default for video callbacks. Sume's docs describe callback_url or webhook_url on each request. A Python submit wrapper.

4 min readSume
All posts

OpenRouter documents two ways to set a video callback: a per-request callback_url, which takes priority, and a workspace default set in workspace settings that applies to requests without one. Sume's video docs describe callback_url on the request and do not describe a workspace-level default. If your OpenRouter client relied on the default, add the URL to every Sume submit.

What each side documents

The facts below come from OpenRouter's video guide and Sume's video docs and Webhooks page. Absence from the docs is not proof that no setting exists, so check the dashboard.

Callback configuration (vendor docs, read 2026-10-09)
ItemOpenRouterSume
Per-request fieldcallback_url, must be HTTPScallback_url on /v1/videos; webhook_url on job routes, with callback_url as an alias
Workspace defaultYes, in workspace settingsNot described in the docs
Envelopevideo.generation.* eventsStandard job envelope: job.completed, job.failed, job.canceled
URL rulesHTTPSPublic HTTPS; localhost and private networks are rejected
Retry dedupeX-OpenRouter-Idempotency-Key = <job_id>-<status>Use job_id as your idempotency key

A submit helper that always sets it

The wrapper puts one constant callback URL on every request and sends an Idempotency-Key, which Sume uses to make retries safe: a replay returns the original job. Sume's /v1/videos returns id, status, and polling_url. The file compiles without a key; run it with SUME_API_KEY set.

import json
import os
import urllib.request

CALLBACK = "https://hooks.example.com/sume"  # public HTTPS only

def submit(model: str, prompt: str, key: str) -> dict:
    body = {"model": model, "prompt": prompt, "callback_url": CALLBACK}
    req = urllib.request.Request(
        "https://api.sume.com/v1/videos",
        data=json.dumps(body).encode(),
        headers={
            "Authorization": f"Bearer {os.environ.get('SUME_API_KEY', '')}",
            "Content-Type": "application/json",
            "Idempotency-Key": key,
        },
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.load(resp)

if __name__ == "__main__":
    job = submit("seedance-2", "A paper boat on a puddle", "boat-2026-10-09-001")
    print(job["id"], job["status"], job["polling_url"])

Other differences to check on the way

Sume model ids are bare catalog ids such as seedance-2, with no provider-org prefix. size, seed, and a non-empty provider.options return 400 unsupported_parameter. The poll status spells cancelled with two Ls, but the job route uses canceled.

Keep polling as a backup in either system. Sume says a webhook is a delivery optimization, and ten refused attempts leave the job finished and the delivery failed.

What to do in migration

Search your OpenRouter workspace settings for a default callback. If one is set, copy it into the code that builds Sume requests. Then verify the first delivery with the Sume signature code, not the OpenRouter one, since the envelope and signing string differ.

Run both systems in parallel for a day if you can, and compare job ids and final states from the pollers before you switch the webhook route.

Sources

Related posts

More in Comparisons

All Comparisons posts

Written by Sume