Vidu Q4 callback_url receiver, moved to Sume's signed webhooks
Vidu and Sume both POST a callback. Sume's needs HTTPS, fires on terminal events only and signs the raw body with HMAC. What to change in the receiver.

Vidu's Q4 Preview page sends a callback to your callback_url whenever the task status changes, signs it, and retries three times on failure. Sume's callback_url must be HTTPS and fires only when the job ends (job.completed, job.failed, job.canceled). A receiver ported from Vidu therefore needs Sume's own signature check and no longer sees progress states.
What changes in the receiver?
Compare the two side by side before you port the handler. Do not reuse Vidu's verification code: the algorithms differ.
| Question | Vidu Q4 Preview | Sume /v1/videos |
|---|---|---|
| Field | callback_url | callback_url, HTTPS only |
| When it fires | every status change, with the latest status | terminal events only, no progress |
| Retries | three retries on a failed send | up to 10 attempts, 30 s apart by default |
| Signature | a callback signature algorithm, defined by Vidu | x-sume-webhook-signature, HMAC SHA 256 over timestamp.raw_body |
| Event names | task states such as success and failed | job.completed, job.failed, job.canceled |
How is a Sume delivery signed?
Sume signs <timestamp>.<raw_body> with HMAC SHA 256 and sends x-sume-webhook-timestamp plus x-sume-webhook-signature: sume-v1=<hex>. During a secret rotation the header holds one sume-v1= entry per live secret, newest first, separated by commas; accept the delivery if any entry matches. Your secret is on the dashboard Webhooks tab or at GET /v1/webhooks/signing-secret. Details are in the webhooks guide.
What does a minimal verifier look like?
Use the raw bytes of the body, not a re-serialized JSON object. The function refuses an empty secret.
import hashlib, hmac, time
def verify(raw_body: bytes, timestamp: str, header: str, secret: str, tolerance=300) -> bool:
if not secret:
return False
try:
ts = int(timestamp)
except ValueError:
return False
if abs(time.time() - ts) > tolerance:
return False
digest = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
want = "sume-v1=" + digest
return any(hmac.compare_digest(p.strip(), want) for p in header.split(","))
body = b'{"event":"job.completed"}'
ts = str(int(time.time()))
sig = "sume-v1=" + hmac.new(b"s3cret", ts.encode() + b"." + body, hashlib.sha256).hexdigest()
print(verify(body, ts, sig, "s3cret"), verify(body, ts, sig, ""))Should you keep polling too?
Yes, as a fallback. Because only the end of a job is delivered, a missed webhook looks like silence. Keep the polling_url from the submit response and check it on a slow timer.
Sources
Related posts
More in Developers
- Vidu Q4 cover_url and watermarked_url vs Sume's poll response
Vidu returns url, cover_url and watermarked_url for 24 hours. Sume's poll returns unsigned_urls and usage. Where a poster still comes from.
- Vidu: failed and moderated videos use no credits. What Sume does
Vidu's pricing page says failed generations, moderation rejections included, use no credits. Sume reserves at submit and releases on failure. How to check each.
- Vidu Q4 image-to-video request, field by field, as a Sume body
Vidu Q4 Preview is not in Sume's video catalog (read 2026-10-09). Map its img2video fields to POST /v1/videos on seedance-2.5 or wan-3.0.
- Vidu task states mapped onto Sume job statuses in Python
Map Vidu task states (created, queueing, processing, success, failed) to Sume queued, processing, completed and failed so an old poller keeps working.
Written by Sume