Documentation topics
Developer docs

Responses and retries

Acknowledging an event

Any 2xx response headers mean success. Verify, atomically deduplicate, and durably save the event or queued work before acknowledging. Do slow bot work asynchronously.

Respond promptly within 10 seconds. This is the whole attempt budget, including connecting and sending the body, not 10 seconds of processing time after your handler starts.

Once Unpoll observes 2xx headers, a later timeout or failure while draining the response body does not turn that success into a retry. Keep your response small; Unpoll does not need a response body.

Which failures retry?

OutcomeHandling
2xx response headersSuccess; no automatic retry.
408, 429, or 5xxRetry within the attempt and age budgets.
Connection errors or timeouts before response headersRetry within the same budgets; your receiver may already have processed the event.
Other non-2xx, including 3xx and other 4xxTerminal failure immediately. Redirects are not followed.

Backoff and the actual retry window

Automatic retries use exponential backoff with jitter. After attempt n, the wait is between half and all of 2^(nāˆ’1) seconds, subject to the bounded retry policy.

A single valid Retry-After header, as delta-seconds or an HTTP date, can lengthen the wait. Unpoll caps it at 1 hour, still subject to the cycle's retry horizon; malformed, duplicate, or oversized values are ignored.

A delivery cycle gets at most 8 attempts total, including the first attempt, with a 24-hour retry horizon. These are upper bounds, not a promise of retries spread across 24 hours. Without Retry-After, persistent errors normally exhaust the attempts after roughly 1 to 3.5 minutes, depending on jitter and request durations. Longer outages need manual redelivery.

The horizon bounds admission and scheduling of attempts. An already admitted request is still bounded by its HTTP and lease deadlines. A claimed attempt consumes its cycle budget even if the worker crashes before sending, so the number of HTTP requests you observe can be less than eight.

What stays the same on retry

Every automatic retry and manual redelivery reuses:

  • The exact same body bytes, including the same stored created_at and schema version.
  • The same event ID.
  • The same delivery ID.

Each attempt is signed with its current timestamp and the current endpoint signing secret. Timestamp and signature can change, but attempts in the same second with the same body and secret can have identical signatures.

Duplicates and ordering

Delivery is at least once, not exactly once. For example, your receiver can accept an event while its 2xx response is lost on the way back. Unpoll must then retry an event you already processed.

Deduplicate atomically on the verified body's id before acting. The ID headers are not covered by the MAC. See verifying signatures for the complete acceptance sequence.

Events can arrive out of order. Use the provider's data.timestamp when occurrence time matters, not webhook arrival order.

Manual redelivery

A failed delivery can be redelivered in the dashboard under Deliveries, while it is retained. A manual redelivery starts a new bounded automatic retry cycle without changing the event, delivery, body, or lifetime attempt history.

There must be an existing delivery and an enabled endpoint. An event received with no enabled endpoint never had a delivery and cannot be backfilled this way. API keys cannot request redelivery; the history routes use dashboard authentication.

Inspect event and attempt history →