Skip to main content

Delivery & retries

Orchestr delivers each event to every subscribed endpoint and retries failed deliveries on a fixed backoff schedule.

What counts as success​

A delivery succeeds when your endpoint returns an HTTP 2xx status within the timeouts below.

LimitValue
Connect timeout3 seconds
Read timeout30 seconds

Anything else is a failure and will be retried, including:

  • any non-2xx status (a 4xx is retried too - your endpoint may be temporarily misconfigured and fixed before attempts run out);
  • connection refused, DNS failure, or TLS errors;
  • a response that exceeds the read timeout.

Retry schedule​

A delivery is attempted up to 6 times (1 initial attempt + 5 retries). After each failed attempt, Orchestr waits before the next one:

After failed attemptWait before next attempt
11 minute
25 minutes
330 minutes
42 hours
524 hours
6- (no further retries)

The total retry window spans roughly 27 hours from the first attempt. The first attempt is made immediately when the event is produced; retries are dispatched by a poller shortly after each backoff interval elapses.

Delivery states​

Each delivery moves through these states:

StateMeaning
PENDINGCreated, awaiting the first attempt.
PROCESSINGCurrently being delivered.
SUCCEEDEDYour endpoint returned 2xx. Final.
FAILEDAn attempt failed; a retry is scheduled.
EXHAUSTEDAll attempts failed. Final - no further delivery.
INVALIDThe event could not be delivered (e.g. the endpoint URL failed validation at send time). Final.

When a delivery reaches EXHAUSTED, Orchestr stops trying. Make sure your endpoint is reliable, and reconcile against the API (for example by searching transactions or orders) if you suspect missed events.

Idempotency​

The same event can be delivered more than once - for example when your endpoint returns 2xx but the connection drops before Orchestr records the success, or when several endpoints are subscribed. Design handlers to be idempotent:

  • De-duplicate on the envelope id. Record processed event IDs and ignore repeats.
  • Make the side effects of processing safe to apply at-least-once (upserts, conditional updates).
  • Do not assume events arrive in order. Use event_time / created and the resource status to decide whether an event is newer than what you have already applied.
on receive(event):
if seen(event.id): # already processed → ack and stop
return 200
verify_signature(event) # reject if it does not verify
persist(event.id) # mark as seen
enqueue_for_processing(event) # do slow work asynchronously
return 200