Action Delivery Lifecycle
When a write matches a subscription’s filter, WarmHub starts a delivery to the subscription’s webhook target. Each delivery is a run, and a run can make several attempts before it succeeds or gives up.
This page is the conceptual reference for that lifecycle — the statuses a run moves through, when WarmHub retries, the error codes you see on a failed attempt, and what happens when a delivery can no longer recover. It applies to any WarmHub action delivery; today that means subscription webhooks.
To inspect the deliveries for a specific subscription, see Managing Subscriptions → Delivery Feed. For the HTTP endpoints that return runs and attempts, see HTTP API → Actions.
Run Statuses
Section titled “Run Statuses”A run carries one status at a time. It starts at pending and moves toward one of three terminal states — succeeded, failed_terminal, or dead_letter.
| Status | Meaning |
|---|---|
pending | Run created, not yet executed |
running | Currently executing |
processing | Accepted by the handler and continuing asynchronously while WarmHub waits for a callback |
retry_wait | An attempt failed; waiting for the next retry |
succeeded | Completed successfully |
failed_terminal | Non-retryable error — no further attempts scheduled |
dead_letter | No recovery path remains |
A run in processing is waiting for your handler to report back through the callback endpoint: a success callback moves it to succeeded, and a failure callback to failed_terminal. Separately, a run that has reached failed_terminal can be moved back to retry_wait for another attempt with a retry_requested callback.
Attempts and Retries
Section titled “Attempts and Retries”Each run makes up to 5 attempts. WarmHub retries an attempt only when its failure is retryable (see Error Codes below) and the attempt budget is not yet spent; otherwise the run goes terminal.
Between retries the run sits in retry_wait. The wait grows exponentially — about one second before the second attempt, doubling for each attempt after that — so a flapping target gets progressively more time to recover. When the attempts are exhausted without a success, the run becomes dead_letter.
A single attempt that returns a non-retryable error (for example, an HTTP_400) skips the remaining budget and goes straight to failed_terminal.
Error Codes
Section titled “Error Codes”These codes appear on a failed attempt, in both wh sub attempts and the run’s lastErrorCode field.
| Code | Retryable | Description |
|---|---|---|
WEBHOOK_NETWORK_ERROR | Yes | Network or transport error while connecting to the webhook target |
WEBHOOK_TARGET_REJECTED | No | WarmHub rejected the target URL at dispatch time because it was not reachable or not allowed. See Webhook URL Requirements for the allowed scheme, ports, and public-network rules. |
WEBHOOK_REDIRECT_LIMIT | No | The webhook target exceeded WarmHub’s redirect-follow limit. This is usually a redirect loop or an overly long redirect chain at the partner endpoint. Fix the destination so it returns a terminal response directly or within a small number of redirects. |
HTTP_<status> | Depends | Remote returned an HTTP response. HTTP_429 and HTTP_5xx are retryable; other HTTP_4xx responses are not |
WEBHOOK_INPUT_NOT_FOUND | No | Could not load execution input for delivery |
Terminal Failure and Fallback
Section titled “Terminal Failure and Fallback”A failed run ends in one of two states. A non-retryable error — an HTTP_400, or a callback that reports failure — sends the run straight to failed_terminal. A retryable error that never succeeds — repeated HTTP_503s, for instance — keeps retrying until the attempt budget is spent, then lands in dead_letter.
A subscription can name a fallback webhook URL. When a run fails terminally, WarmHub posts a notification to that URL describing the failed delivery — the run, the error, and how many attempts were made. This is an out-of-band alert, not a retry: the original webhook is not re-sent, and the run stays in its failure state regardless of how the fallback responds.
If WarmHub can’t use the fallback URL at all — it’s rejected by webhook URL validation, or it exceeds the redirect-follow cap — a failed_terminal run is promoted to dead_letter. (A fallback that is only momentarily unreachable, such as a 5xx, is retried rather than promoted.) In full, a run reaches dead_letter when:
- a retryable delivery exhausted its attempts without succeeding,
- a
failed_terminalrun’s fallback URL was rejected by webhook URL validation, or - a
failed_terminalrun’s fallback URL exceeded the redirect-follow cap.
Set a fallback URL when you create or update the subscription: wh sub create / wh sub update --fallback-webhook-url, the SDK client.subscription surface, or the warmhub_subscription_create / warmhub_subscription_update MCP tools.
WarmHub records a repo-scoped action notification once a delivery’s failure is final — once no retry or fallback can still change the outcome. Subscriptions with notifyOnSuccess enabled also record successful deliveries. See Managing Subscriptions → Notifications to read these records.
Hit a problem or have a question? Get in touch.