Webbu Docs

Delivery & Retries

How Webbu delivers batches, what counts as a failure and how retries work.

Delivery flow

When a flush condition is met, Webbu:

  1. Locks the group so only one worker delivers it
  2. Loads the items, oldest first, up to batch.maxItems
  3. Sends one request to delivery.url
  4. On success, removes the delivered items and records metrics
  5. On failure, schedules a retry or moves the batch to the dead letter queue

Request format

POST /webhooks/batch HTTP/1.1
Content-Type: application/json
x-idempotency-key: grp:b_orders:9a1c0b7e3d5f2a48:0:v3

delivery.method can be POST (default) or PUT. Any delivery.headers you configured are added, and a signature header if request signing is enabled.

Body:

{
  "webbu": {
    "customerId": "c_your_account",
    "projectId": "p_shop",
    "bufferId": "b_orders",
    "groupKey": "cus_001",
    "groupHash": "9a1c0b7e3d5f2a48",
    "segment": 0
  },
  "window": {
    "firstItemAt": 1767225600000,
    "lastActivityAt": 1767225604000,
    "itemsCount": 2
  },
  "items": [
    { "messageId": "5b0c6f0e-…", "receivedAt": 1767225600000, "payload": { "event": "order.created" } },
    { "messageId": "c2d8e1a0-…", "receivedAt": 1767225604000, "payload": { "event": "order.paid" } }
  ]
}
FieldDescription
webbuWhere the batch comes from: account, project, buffer, group key and segment
window.firstItemAt / lastActivityAtWhen the group received its first and its latest item (epoch ms)
window.itemsCountNumber of items in this request
items[].messageIdThe item's message ID (from messageId in the body, X-Idempotency-Key, or generated)
items[].receivedAtWhen Webbu accepted the item (epoch ms)
items[].payloadThe JSON body exactly as Webbu parsed it. The original request headers are not forwarded

Success and failure

A delivery succeeds when your endpoint returns any 2xx status within 10 seconds. The timeout is fixed.

ResultWhat Webbu does
2xxSuccess: items are removed from the buffer
408, 429Retry with backoff
5xxRetry with backoff
Timeout (10 s), connection refused or resetRetry with backoff
Other 4xx (e.g. 400, 401, 404)No retry: moved to the DLQ right away
3xx redirectsNot followed and not retried: moved to the DLQ
Other network errors (e.g. DNS or TLS failures)No retry: moved to the DLQ

Retries and backoff

throughput.maxAttempts is the total number of attempts, including the first (default 3, maximum 5). The delay before retry n is:

delay(n) = min(initialMs × multiplier^(n−1), maxMs)

With the defaults (initialMs: 1000, multiplier: 2, maxMs: 60000):

AttemptWhen
1When the flush condition is met
21 s after attempt 1 fails
32 s after attempt 2 fails
(DLQ)After attempt 3 fails

With maxAttempts: 5 and the same backoff, the retries wait 1, 2, 4 and 8 seconds. Raise initialMs (for example to 30000) if your endpoint needs more time to recover.

Each retry loads the group's items again, so a retry can include items that arrived after the first attempt.

Idempotency

The idempotency header (default x-idempotency-key, configurable as delivery.idempotencyHeader) has the form grp:{groupId}:v{n}. It stays the same across retries of the same flush and changes after the group is flushed.

Delivery is at least once: if your endpoint processes a batch but answers too late, the batch is sent again. Make processing idempotent, ideally per item using items[].messageId.

Recommendations for your endpoint

  • Acknowledge quickly: return 2xx within 10 seconds and process asynchronously if needed
  • Be idempotent: deduplicate by items[].messageId
  • Accept large bodies: a batch can hold up to maxItems payloads
  • Use status codes deliberately: return 5xx or 429 for temporary problems (retried) and 4xx only for requests that will never succeed (not retried)

On this page