Retries and failures
A delivery counts as received only when your endpoint answers 2xx in time. Everything else is retried on a fixed schedule — except 410 Gone.
What your answer means
| Your endpoint answers | OfficeHero does |
|---|---|
2xx within 15 seconds | Marks the delivery delivered. The response body is ignored. |
429 Too Many Requests | Waits for your Retry-After (at most one hour; 60 seconds if absent) and tries again without using up an attempt. |
410 Gone | Stops: the delivery ends failed (error_class: gone) and is not retried. Three 410s within 24 hours disable the endpoint. |
Any other 4xx or 5xx, a 3xx redirect, a timeout or a connection error | The delivery stays pending and is retried on the schedule below. Redirects are never followed. |
Timeouts
Your endpoint has 10 seconds to accept the connection and 15 seconds
in total to answer. Acknowledge first and process afterwards — put the event
on a queue and return 204.
Retry schedule
A delivery is tried up to 8 times. After a failed attempt it stays
pending and the next attempt waits, with ±20 % jitter:
| After attempt | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Next attempt in | 1 min | 5 min | 15 min | 1 h | 3 h | 6 h | 12 h |
If the 8th attempt fails the delivery is marked dead. You can send it again
any time with replay. Each attempt carries
OH-Event-Attempt (1, 2, …) and is signed when it is sent, with a fresh
t, so a retry passes the 300-second timestamp check.
Deliveries that are not retried
A delivery can also end failed without your endpoint being called when
its attempt comes up:
error_class | Why |
|---|---|
endpoint_disabled | The endpoint was paused, deleted or disabled. Pausing does not hold queued deliveries. |
ssrf_rejected | The endpoint’s host now resolves to a private or reserved address. |
api_key_inactive, api_key_scope_insufficient | The API key that owns the endpoint was revoked, expired, or no longer has the event’s scope. |
pii_capability_blocked | The event carries personal data and the endpoint is no longer enabled for it. |
Fix the cause, then replay what you missed.
Automatic disabling
An endpoint is switched to disabled — and the company’s admins notified —
when:
- 50 attempts in a row have failed (
auto_consecutive_failures). Every failed attempt counts, not every failed delivery: one event retried 8 times adds 8. A2xxresets the count; - it has answered
410 Gone3 times within 24 hours (auto_410_repeat); - every delivery created for it in the last 7 days has ended
failedordead(auto_7_day_all_dead). On a quiet endpoint this can be a single dead delivery, if it was the only one that week.
disabled_reason says which. Fix the receiver, then
POST /v1/webhook_endpoints/{id}/resume (which takes a place in the endpoint
quota again), and replay what you missed.
Duplicates and ordering
- Deliveries can arrive more than once — after a timeout on your side, a
replay, or a retry that raced your slow
2xx. Deduplicate on the envelopeid(also sent asOH-Event-IdandIdempotency-Key). - Order is not guaranteed. A retried delivery can arrive after a later
event. Use
occurred_atand the resource’supdated_at, or fetch the current state from the API by id.
Watching deliveries
GET /v1/webhook_endpoints/{id}/deliveries lists deliveries newest first with
their status, the attempt count, the status your endpoint answered, how long
it took and the error class (http_5xx, http_4xx, redirect_blocked,
timeout, connect_failed, tls, dns, transport, rate_limited, gone,
and the ones in the table above).