100 items, concurrency 16: size your webhook receiver for the burst
A 100-item Format bulk run at concurrency 16 runs in 7 waves, and children can finish in clusters. Ack fast, store, queue, and cap your own worker pool.

A Format bulk run keeps up to 16 child runs in flight, so up to 16 webhooks can land within moments of each other, and your receiver should expect that. A 100-item queue at a concurrency of 16 is 7 waves (6 full waves of 16 plus a last wave of 4), and each finished child starts the next item at once, so completions arrive in clusters rather than a steady trickle. The right design is the one Stripe's webhook guide describes: return a 2xx quickly, then process events asynchronously through a queue at a rate your system supports.
The numbers
The window never goes above concurrency, but your plan's generation concurrency still applies to the children, so the effective burst is often smaller than 16. The receiver should be built for the ceiling. Sume's delivery timeout is 10 seconds per attempt and a slow endpoint spends the retry budget, so a handler that calls a database, a model and an invoicing API inline will cause duplicate deliveries just when the load is highest.
| Quantity | Value | Source |
|---|---|---|
| Items per queue | 1-100 | Sume bulk-runs docs |
| Concurrency window | 1-16 | Sume bulk-runs docs |
| Waves for 100 items at 16 | 7 (6 x 16 + 4) | Computed |
| Run webhook attempts | 10, then exhausted | Sume run-webhooks docs |
| Attempt timeout | 10 seconds | Sume run-webhooks docs |
| Dedupe key | request_id, same on each retry | Sume run-webhooks docs |
A receiver that holds up
Structure the endpoint in three steps: verify the signature on the raw body, insert a row keyed by request_id (ignore conflicts), return 200. A separate worker reads unprocessed rows with a fixed pool size and fetches each run's result from result_url. If the pool is smaller than the burst, rows wait in your table, not in Sume's retry loop.
Because a run webhook fires once when the run completes or fails, and a canceled run never sends one, the queue itself is the source of truth for completion. Poll GET /v1/format-run-queues/{id} and reconcile counts against the number of webhook rows you hold; a gap means a missing delivery, which you fix with redeliver or a direct read of the child run.
- Store first, process later. Never do model or media work in the request.
- Cap your worker pool so a burst of 16 cannot fan out into 16 downstream calls.
- Reconcile queue
countswith received rows after the queue reportscompleted. - Remember that
completedmeans every item is terminal, not that every item succeeded.
Why this gets more important
Cheaper clips and bigger batches push more completions into the same minute. The receiver is usually the part nobody sized, because it looked trivial when the batch was ten items.
Sources
Related posts
More in Formats
- Accept a Format grant: personal key 403, wrong workspace 404
POST /v1/format-grants/{id}/accept needs formats:write and a team-workspace key. A personal key gets 403, and a grant meant for another workspace reads as 404.
- App ad from a store link: sume-mobile-app-ugc and the 30-file budget
Calling the sume-mobile-app-ugc Format with an app page URL and screenshots: what counts toward the 30-file budget, what does not, and the idempotency key.
- Audit who can run your Format: list grants, pending versus accepted
GET .../grants lists pending and accepted workspace grants on a Format, newest first, without revoked ones. Script a weekly audit and revoke what is stale.
- Back-in-stock video for one SKU: send the facts as input, not prose
One restock clip is one Format run: put SKU, stock count and ship date in an input object, add a short instruction, and key the run by SKU and date.
Written by Sume