Skip to content

Action Delivery Lifecycle

When a write matches a subscription’s filter, WarmHub starts a delivery to the subscription’s webhook target. For metadata events (such as a repo.renamed or org.renamed event), delivery is triggered by the event itself — the backend does not accept a filter on metadata-event subscriptions, so no filter match is required. A delivery has its own identity — a deliveryId — and an associated run that is created and attached before the first attempt is enqueued. Because the run and its runId are persisted on the delivery before execution begins, feeds can show a run-associated delivery that has not yet started executing.

For lease-managed subscriptions (subscriptions where your handler claims work by deliveryId rather than receiving a pushed webhook), a delivery may exist and be claimable before any run has been created. In that case the delivery carries its own status independently of any run status. Once a run is created for the delivery, the run status drives the outcome.

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 or suppressed 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.

Before a run exists — or while a lease-managed delivery is waiting to be claimed — the delivery itself carries a status. These delivery-status values appear on the Delivery Feed. Note that wh sub log renders the run state (pending, running, succeeded, etc.) once a run is attached, so delivery-only statuses such as managed_dispatched or completed are visible only before a run exists:

StatusMeaning
lease_waitDelivery is waiting for a lease to become available
leasedA handler has claimed the delivery by deliveryId and holds the lease
managed_dispatchedA run has been created and attached to the delivery; the first attempt is enqueued
leased_timeout_fallbackThe lease timed out; WarmHub is preparing a fallback attempt
completedThe delivery’s run succeeded. This status marks success only — terminal failures surface through the run’s failure states (failed_terminal, dead_letter, or suppressed), not as completed

Once a run is attached (managed_dispatched and beyond), the run status described in the next section drives the outcome.

A run carries one status at a time. It starts at pending and moves toward one of four terminal states — succeeded, failed_terminal, dead_letter, or suppressed.

StatusMeaning
pendingRun created, not yet executed
runningCurrently executing
processingAccepted by the handler and continuing asynchronously while WarmHub waits for a callback
retry_waitAn attempt failed; waiting for the next retry
succeededCompleted successfully
failed_terminalNon-retryable error — no further attempts scheduled, though a fallback may still recover the run
dead_letterNeeds a human: either the attempt budget was spent without success, or the failure is permanent and cannot be retried into a different outcome
suppressedDelivery was blocked before execution — no webhook was sent and no retry will occur. See WEBHOOK_AUTHORITY_REVOKED for the primary cause.

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 dead_letter. A handler that took the delivery and then reported failure — or never called back at all, which times out — has produced a permanent failure that retrying cannot change, so the run moves to dead_letter for human review. Separately, a run that has failed terminally can be moved back to retry_wait for another attempt with a retry_requested callback; this works from either terminal failure status, and is still refused once the attempt budget is spent.

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. The two callback failure classes skip it as well, but land in dead_letter instead: the handler already accepted the delivery, so the failure is permanent and belongs in dead_letter for human review.

These codes appear on a failed or suppressed attempt, in both wh sub attempts and the run’s lastErrorCode field. Attempts recorded with status suppressed (see WEBHOOK_AUTHORITY_REVOKED below) are distinct from failed attempts — they represent deliveries that were blocked before the webhook was sent rather than attempts that were made and rejected.

CodeRetryableDescription
WEBHOOK_NETWORK_ERRORYesNetwork or transport error while connecting to the webhook target
WEBHOOK_TARGET_REJECTEDNoWarmHub 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_LIMITNoThe 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>DependsRemote returned an HTTP response. HTTP_429 and HTTP_5xx are retryable; other HTTP_4xx responses are not
WEBHOOK_INPUT_NOT_FOUNDNoCould not load execution input for delivery
WEBHOOK_CREDENTIAL_ERRORYesA transient error occurred while resolving the webhook binding’s credentials — for example, a temporary failure fetching a secret. WarmHub will retry the attempt.
WEBHOOK_CREDENTIALS_REVOKEDNoThe webhook binding’s credentials have been revoked. WarmHub will not attempt delivery and the run goes straight to failed_terminal. Re-authorize the binding to resume delivery. For details on how revoked credentials interact with delivery — including when WarmHub proceeds without auth headers versus blocking delivery entirely — see Subscription Credentials.
WEBHOOK_AUTHORITY_REVOKEDNoBefore dispatching the webhook, WarmHub re-derives the subscription authority and finds it is no longer valid. WarmHub does not send the webhook. The run and the attempt are both recorded with status suppressed and this error code. No retry is scheduled. Some subscriptions tie their authority to an external install or integration; for those, delivery continues as long as that external authorization remains active, even if the token originally used to create the subscription has since been revoked. To resume delivery for a suppressed subscription, re-authorize the subscription with a valid token. For details on how subscription authority is established and what can invalidate it, see Subscription Credentials.
CALLBACK_FAILURENoThe handler accepted the delivery (the run entered processing) and then reported failure via the callback endpoint. Because the handler already took ownership of the delivery, the failure is permanent — the run moves to dead_letter and no further attempts are scheduled.
CALLBACK_TIMEOUTNoThe handler accepted the delivery but did not call back within the allowed window. WarmHub records this code on the attempt and moves the run to dead_letter. Like CALLBACK_FAILURE, this is treated as a permanent outcome: the handler held the delivery and did not complete it, so the run moves to dead_letter for human review rather than being retried.

A failed run ends in one of three states. A non-retryable error — an HTTP_400, a WEBHOOK_CREDENTIALS_REVOKED, 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 pre-dispatch authority check failureWEBHOOK_AUTHORITY_REVOKED — moves the run directly to suppressed without sending the webhook and without scheduling any retry.

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_terminal run’s fallback URL was rejected by webhook URL validation,
  • a failed_terminal run’s fallback URL exceeded the redirect-follow cap, or
  • a failed_terminal run caused by WEBHOOK_CREDENTIALS_REVOKED had a fallback-enabled subscription — WarmHub marks the run dead_letter without calling the fallback URL.

Runs that end in suppressed (caused by WEBHOOK_AUTHORITY_REVOKED) are not eligible for fallback delivery.

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. This applies to runs that end in failed_terminal or dead_letter; successful runs and runs that end in suppressed (caused by WEBHOOK_AUTHORITY_REVOKED) do not produce an action notification. See Managing Subscriptions → Notifications to read these records.