Delivery and retries
What your endpoint must do
Respond 2xx. Any other status — and any connection error or timeout — counts as a failed delivery and is retried. A 3xx redirect is not followed.
Respond within 10 seconds. Each attempt has a 10-second timeout. Don't do real work on the request thread: verify the signature, enqueue the event, return 200. Processing inline is the most common cause of retry storms, because slow handlers time out, get retried, and add load that makes them slower.
Be idempotent. Dealops may deliver the same event more than once. Dedupe on X-Dealops-Event-Id, which is stable across every retry and replay of an event. Do not dedupe on X-Dealops-Delivery-Id — that changes per delivery, so using it as the key defeats deduplication entirely.
Don't assume ordering. Retries and backoff mean a later event can arrive before an earlier one. If order matters, use the occurredAt timestamp in the payload rather than arrival order.
Retries
A failed delivery is retried up to 8 attempts with exponential backoff starting at 30 seconds, so the gaps widen from about half a minute to roughly an hour — about a two-hour window in total from first attempt to last. The current attempt number arrives in X-Dealops-Attempt, starting at 1.
After the eighth failure the delivery is marked exhausted and abandoned. That event won't be retried again.
Delivery statuses
Deliveries are inspectable through GET /webhook-configs/{id}/deliveries:
| Status | Meaning |
|---|---|
PENDING | Queued, or waiting between retries. |
SUCCESS | Your endpoint returned 2xx. |
FAILED | An attempt failed; more retries remain. |
EXHAUSTED | All 8 attempts failed, or the payload was undeliverable. |
Each record carries the diagnostics you need to debug a failure without asking us: lastResponseStatus, lastResponseExcerpt (the beginning of your response body), lastErrorMessage, lastDurationMs, lastAttemptedAt, and nextAttemptAt.
Delivery history is kept for 30 days, then deleted. Anything you need long-term should be recorded on your side as you receive it.
When endpoints get disabled
Failures are counted per endpoint, not per attempt: one delivery that exhausts all 8 attempts counts as a single failure. A successful delivery resets the counter to zero.
After 20 consecutive failures the endpoint is automatically disabled and stops receiving events. That's roughly 20 events, each having failed for two hours — a sustained outage, not a blip.
Endpoint status is one of:
| Status | Behaviour |
|---|---|
ACTIVE | Receiving events. |
PAUSED | Not receiving events. You can pause and resume an endpoint deliberately. |
DISABLED | Auto-disabled after 20 consecutive failures, or revoked. |
consecutiveFailures is on the endpoint record, so you can alarm on it climbing rather than discovering the problem when deliveries stop. disabledReason explains an auto-disable after the fact.
Recovering from an auto-disable: fix the receiver (or correct the URL), then set the endpoint back to ACTIVE with PUT /webhook-configs/{id}. That clears consecutiveFailures and disabledReason, so a single early failure won't immediately re-trip the threshold. Events that were dropped while the endpoint was disabled are not backfilled — reconcile anything you missed through the API.
Test deliveries are sent even when an endpoint is paused, so you can pause a production endpoint, exercise your handler with a test event, and resume — without real events firing at a receiver you're mid-way through changing.
Payload size
Deliveries are capped at 1 MB. An event whose payload exceeds it is marked EXHAUSTED and not delivered — retrying wouldn't make it smaller. This is rare and generally means an unusually large quote. If you see it, get in touch rather than working around it.
Rotating the signing secret
POST /webhook-configs/{id}/rotate-secret returns a new secret and returns it only once.
There is no overlap window: the moment you rotate, every subsequent delivery is signed with the new secret and the old one stops working. Deliveries signed with the new secret will fail verification until your receiver has it.
To rotate without dropping events, either accept both the old and new secret in your verifier during the cutover — deploy that first, rotate, then remove the old one — or pause the endpoint, rotate, deploy, and resume. Rotating first and deploying afterwards means every delivery in between fails verification and burns retries.
URL safety
Your endpoint URL is validated when you register it and re-validated on every delivery. HTTPS is required, and hosts resolving to private, loopback, or link-local addresses are refused. Re-checking at delivery time means a hostname that later resolves into a private range stops being delivered to, rather than becoming a way to reach inside our network.
If you change where your endpoint lives, update the URL rather than repointing DNS at something non-public.