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.

4 min readSume
All posts

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.

Receiver differences (Vidu page and Sume docs read 2026-10-09)
QuestionVidu Q4 PreviewSume /v1/videos
Fieldcallback_urlcallback_url, HTTPS only
When it firesevery status change, with the latest statusterminal events only, no progress
Retriesthree retries on a failed sendup to 10 attempts, 30 s apart by default
Signaturea callback signature algorithm, defined by Vidux-sume-webhook-signature, HMAC SHA 256 over timestamp.raw_body
Event namestask states such as success and failedjob.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

All Developers posts

Written by Sume