Higgsfield hf_webhook query parameter vs Sume callback_url
Higgsfield takes the webhook as an hf_webhook query parameter; Sume takes callback_url in the body and signs the delivery. Payload shapes and a Python verifier.

On Higgsfield you register a webhook per request by passing an HTTPS endpoint in the hf_webhook query parameter when you submit. On Sume's /v1/videos route you pass callback_url in the request body, which must be HTTPS, and Sume POSTs a signed job event to it when the job reaches a terminal state.
The two envelopes
Higgsfield's envelope has four fields: request_id, status (completed, failed or nsfw), error (null on success) and payload, where video outputs sit under payload.video with a URL and content type. The webhook page read for this post does not describe a signature header.
| Item | Higgsfield | Sume |
|---|---|---|
| Where you set it | hf_webhook query parameter | callback_url body field on /v1/videos; webhook_url with mode: "webhook" on generate routes |
| Events | Terminal status in the envelope | job.completed, job.failed, job.canceled |
| Identifiers | request_id | request_id and job_id |
| Result | payload.video | payload.artifacts[] with url, type, content_type |
| Signing | Not described on the page | HMAC SHA 256 over <timestamp>.<raw_body> |
Sume's signed delivery
Sume sends terminal events only, with no progress deliveries. A failed or canceled event uses status: "ERROR" with an error object. Two headers carry the proof: x-sume-webhook-timestamp and x-sume-webhook-signature with a value like sume-v1=<hex>. During a secret rotation the header can carry several comma-separated entries, and you accept the delivery if any one matches. Reject a timestamp outside a replay window; five minutes is the documented default.
A Python verifier
Verify against the raw bytes of the body, before any JSON parsing. This function refuses an empty secret and handles rotation.
import hashlib, hmac, time
def verify(raw_body: bytes, timestamp: str, header: str, secret: str, tolerance: int = 300) -> bool:
if not secret:
raise ValueError("signing secret is empty")
try:
ts = int(timestamp)
except ValueError:
return False
if abs(time.time() - ts) > tolerance:
return False
msg = f"{ts}.".encode() + raw_body
digest = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
expected = f"sume-v1={digest}"
return any(hmac.compare_digest(e.strip(), expected) for e in header.split(","))
if __name__ == "__main__":
body = b'{"event":"job.completed"}'
ts = str(int(time.time()))
sig = "sume-v1=" + hmac.new(b"s", ts.encode() + b"." + body, hashlib.sha256).hexdigest()
print(verify(body, ts, sig, "s"))
Where the secret comes from
Your signing secret comes from the dashboard Webhooks tab or from GET /v1/webhooks/signing-secret with a key that carries account:read. Use job_id as your idempotency key, and keep status polling available in case a delivery never arrives.
Sources
Related posts
More in Developers
- Higgsfield Idempotency-Key: 422 on a changed body, vs Sume 409
Both APIs replay the original job for a repeated Idempotency-Key. A changed body gets 422 on Higgsfield and 409 idempotency_conflict on Sume. Rules compared.
- Higgsfield output URLs last at least 7 days: copy files out
Higgsfield keeps generated output for at least seven days and may remove it later. Where Sume serves a finished video, and why to copy it to your own storage.
- Higgsfield polling: 2 to 10 seconds with jitter, vs Sume
Higgsfield says start polling at 2 seconds, grow to 10, add jitter. Sume's video docs poll every 30 seconds and honor next_poll_after_seconds. How to pick.
- Higgsfield statuses: nsfw and canceled mapped to Sume job states
Higgsfield returns queued, in_progress, completed, failed, nsfw or canceled. How each maps to Sume's video and job statuses, including cancelled vs canceled.
Written by Sume