Luma API callback_url vs Sume callback_url: signing and retries

Luma's video API takes a callback_url, and so does Sume's /v1/videos. What Sume's callback is signed with, how often it retries, and a Python verifier.

4 min readSume
All posts

Both APIs accept a callback_url, but the guarantees differ. On Sume, the URL must be public HTTPS, the delivery is signed with HMAC SHA 256 over the timestamp and raw body, and a failed delivery is attempted up to 10 times in total. The Luma video generation docs, read on 2026-10-03, list callback_url among the generation parameters without the details covered here.

What Luma documents

The Luma page lists the models ray-2 and ray-flash-2, resolutions of 540p, 720p, 1080, and 4k, and parameters for keyframes, loop, concepts, and callback_url. It says extension works only for generated videos and that images must be CDN URLs. The page lists Ray 2 only, so it may lag newer Luma models.

What Sume documents

The Sume video docs say callback_url must be HTTPS and that Sume POSTs once the job reaches a terminal state. The webhooks docs give the rest.

Sume webhook delivery facts (from the Sume docs)
ItemBehavior
URL rulePublic HTTPS only; localhost and private networks are rejected
Eventsjob.completed, job.failed, job.canceled; terminal only
Signaturex-sume-webhook-signature: sume-v1=<hex> over <timestamp>.<raw_body>
Timestamp headerx-sume-webhook-timestamp; reject outside a replay window (five minutes suggested)
RetriesUp to 10 attempts, fixed 30 s spacing by default, 10 s timeout per attempt
IdempotencyUse job_id as the key on your side

Verify the callback in Python

The verifier below refuses an empty secret, checks the timestamp window, and compares every sume-v1= entry, since the header can carry two during a secret rotation. Pass the raw request body bytes, not a re-serialized JSON object.

import hashlib
import hmac
import time


def verify(raw_body: bytes, timestamp: str, signature_header: str, secret: str, tolerance: int = 300) -> bool:
    if not secret:
        return False
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    if abs(int(time.time()) - ts) > tolerance:
        return False
    digest = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    expected = f"sume-v1={digest}"
    matched = False
    for entry in signature_header.split(","):
        if hmac.compare_digest(entry.strip(), expected):
            matched = True
    return matched

Choosing between callback and polling

Callbacks save polling traffic and give faster notice, but they need a public HTTPS endpoint that is always up. Polling needs no public endpoint, which suits scripts and local development, but wastes calls on long jobs.

Many integrations use both: a callback for the fast path and a slow poll as a backstop. The Sume video docs suggest a polling interval around 30 seconds for video, since jobs commonly take from 30 seconds to several minutes, depending on the model and parameters.

  • Server with a stable HTTPS endpoint: callback plus slow poll.
  • Script or notebook: poll only.
  • Both: dedupe on job_id.
  • Never trust an unsigned callback.

Keep polling as a backstop

Do not rely on a callback alone. The Sume docs say delivery is an optimization, never the only recovery path, and recommend keeping status polling available. The same rule applies to any vendor callback, Luma's included. Keep a poll loop that wakes up for jobs with no terminal event after your own deadline.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume