Retry Policy
Understand delivery attempts, acknowledgements, and duplicate events.
#Schedule
Three attempts max. Schedule:
| Attempt | When |
|---|---|
| 1 | At result-ready time |
| 2 | 5 seconds after attempt 1 fails |
| 3 | 5 minutes after attempt 2 fails |
After attempt 3 fails, the delivery is marked failed and not retried.
A 1-hour hard ceiling from the first attempt also caps total time. If a delivery is still pending past 1 hour after first attempt, it is force-failed.
#Success
Any 2xx response from your endpoint marks the delivery delivered. We do not retry on 2xx.
#What we retry
Transient failures get re-enqueued per the schedule above:
5xxHTTP responses- Connection errors (DNS resolution, refused, reset, peer reset)
- Read or connect timeouts (5s connect / 20s read per attempt)
- Other transport-level errors
#What we do NOT retry
Permanent failures move straight to failed:
4xxHTTP responses (treated as "your code rejected this delivery; retrying won't help")- TLS / certificate errors (expired, hostname mismatch, custom CA)
- SSRF rejections at delivery time (URL resolved to a private / loopback / IMDS address)
- Your account has no active webhook signing secret
#Headers each retry sends
Every attempt for a given delivery sends the same Clodo-Webhook-Id header. Use that value on your end to dedup if you process and a 2xx response is lost in flight.
The Clodo-Signature is recomputed per attempt with a fresh t=. Within the 5-minute replay window the signature stays valid against the same body bytes.
#Reconciling lost deliveries
If your receiver is down past the retry window, the delivery is failed but the underlying job result is still durable. Use Async Polling to fetch the result via GET /v1/<endpoint>/{job_id}/. The polling response carries the same body as the webhook would have delivered (without the id / event_type wrapper).