Know a Sume bulk batch is done: count terminal webhooks vs item count

A Sume bulk queue has no webhook. Put a webhook_url on each item, verify the signature, dedupe by request_id and count terminal events to the item count.

4 min readSume
All posts

How do I get a done signal for a whole bulk batch?

The queue itself sends nothing. communication.webhook_url is per item, so each child run delivers its own format.run.terminal event when it finishes. The queue's own status is only available by polling GET /v1/format-run-queues/{id}, where completed means every item is terminal, not that every item worked.

To get a push-style batch signal, count. Know the item count when you submit, count distinct terminal events as they arrive, and call the batch done when the two match.

What must the receiver do?

  • Verify the signature on the raw body: header x-sume-webhook-signature is sume-v1=<hex>, an HMAC-SHA256 of <timestamp>.<raw body> using x-sume-webhook-timestamp, within 300 seconds. Refuse an empty secret.
  • Dedupe on the envelope request_id, which equals the run id and is the same on every retry.
  • Read outcome (ok, degraded or error) to tally successes and failures.
  • Treat items that never started (run_id: null) as terminal failures on your side; they send no webhook.

What does the counter look like?

This standalone sketch verifies, dedupes and counts. Because it has no web framework, call handle(raw, timestamp, signature) from your own route.

import hashlib, hmac, json, os, time

SECRET = os.environ.get("SUME_WEBHOOK_SECRET", "")
seen, outcomes = set(), {}

def handle(raw: bytes, ts: str, sig: str) -> bool:
    if not SECRET:
        raise RuntimeError("refusing to run with an empty secret")
    if abs(time.time() - int(ts)) > 300:
        return False
    mac = hmac.new(SECRET.encode(), ts.encode() + b"." + raw,
                   hashlib.sha256).hexdigest()
    if not hmac.compare_digest("sume-v1=" + mac, sig):
        return False
    ev = json.loads(raw)
    if ev["event"] == "format.run.terminal":
        seen.add(ev["request_id"])
        outcomes[ev["request_id"]] = ev.get("outcome")
    return True

def batch_done(expected: int) -> bool:
    return len(seen) >= expected

What about stragglers?

Late events are normal: a run can take many minutes, and with concurrency 4 a 100-item batch dispatches in 25 waves. Do not fire your downstream step on a timer; fire it when the counter reaches the expected number, and keep a polling fallback so a lost webhook cannot hold the batch forever.

If the count stalls, poll the queue and read counts and each item's run_id; a run whose webhook failed can be redelivered with POST /v1/format-runs/{id}/webhook/redeliver. Set the expected count to the items that got a run id, since the others will never send an event.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume