> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stableyard.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook delivery and retries

> How Stableyard retries a failed delivery, when it gives up, and how to requeue a delivery once fixed.

Stableyard retries a failed delivery with exponential backoff until it succeeds or reaches the deployment's attempt limit, then abandons it. Failed and abandoned deliveries stay visible for audit and can be requeued once the endpoint is fixed.

## How each response is handled

| Your endpoint's outcome | What Stableyard does |
| - | - |
| `2xx` | Delivered. Not sent to that endpoint again |
| `408`, `425`, `429` or any `5xx` | Retried with backoff |
| A network failure, or no response in time | Retried with backoff |
| Any other status, including a `3xx` redirect | Abandoned immediately |
| The URL resolves to a local, private or metadata address | Abandoned immediately |

Replaying a request the endpoint has already rejected as invalid is not expected to succeed, which is why other `4xx` responses are not retried. Respond promptly: an attempt that gets no response in time counts as a network failure.

## Backoff and the attempt limit

The wait after each failed attempt doubles, from about 30 seconds to a ceiling of one hour:

| After failed attempt | Next attempt no sooner than |
| - | - |
| 1 | 30 seconds |
| 2 | 1 minute |
| 3 | 2 minutes |
| 4 | 4 minutes |
| 5 | 8 minutes |
| 6 | 16 minutes |
| 7 | 32 minutes |
| 8 and later | 1 hour |

A `Retry-After` header on a retryable response, in seconds or as an HTTP date, replaces the computed wait and is honoured up to the same one-hour ceiling.

The attempt limit is per deployment. Read `capabilities.webhooks.deliveryPolicy.maxAttempts` from `GET /v2/partners/config` rather than assuming a number:

```json theme={null}
"deliveryPolicy": {
  "guarantee": "at_least_once",
  "maxAttempts": 10,
  "localPrivateDestinationsAllowed": false,
  "dedupe": {
    "deliveryRetryHeader": "x-stableyard-delivery",
    "eventHeader": "x-stableyard-event-id"
  }
}
```

When an attempt fails and the limit is reached, the delivery is abandoned.

## Delivery statuses

| `status` | Meaning |
| - | - |
| `pending` | Queued, or an attempt is in flight |
| `failed` | The last attempt failed and another is scheduled at `nextAttemptAt` |
| `delivered` | Your endpoint returned `2xx` |
| `abandoned` | No more automatic attempts: a non-retryable response, an unsafe destination, the attempt limit, or an endpoint change |

A retry is the same delivery. It keeps the `x-stableyard-delivery` ID, the `x-stableyard-event-id` and the body; only `x-stableyard-timestamp` and the signature are new. If one endpoint succeeds while another subscribed to the same event fails, only the failing endpoint is retried.

## Endpoint restrictions

Endpoints must be HTTPS with no embedded credentials. Redirects are not followed, and destinations that resolve to local, private or metadata addresses are rejected outright, so a tunnel to a laptop will not receive deliveries in a deployed environment.

These rules are checked when you create the endpoint and again before every attempt.

## Inspect deliveries

```bash theme={null}
curl "https://staging-api-v2.stableyard.fi/v2/console/webhooks/webhook_123/deliveries?limit=20" \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{
  "webhookEndpoint": { "id": "webhook_123", "status": "active" },
  "deliveries": [
    {
      "id": "delivery_123",
      "webhookEndpointId": "webhook_123",
      "outboxEventId": "outbox_123",
      "eventName": "payment.succeeded",
      "status": "abandoned",
      "attempts": 10,
      "responseStatus": 503,
      "createdAt": "2026-10-01T12:00:00Z",
      "updatedAt": "2026-10-01T17:30:00Z"
    }
  ],
  "limit": 20,
  "hasNextPage": true,
  "nextCursor": "delivery_123"
}
```

Deliveries are listed most recently updated first. `limit` is up to 100, default 50; pass `nextCursor` as `cursor` for the next page. Each delivery also carries the `requestBody` that was sent, the start of your endpoint's `responseBody`, `nextAttemptAt` while a retry is scheduled, and `deliveredAt` once delivered.

## Requeue a delivery

Once the endpoint is fixed, requeue a failed or abandoned delivery from the Partner Dashboard, or from your backend:

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/console/webhooks/webhook_123/deliveries/delivery_123/retry \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{ "webhookId": "webhook_123", "deliveryId": "delivery_123", "status": "pending" }
```

The same delivery is sent again with its attempt count reset, so it gets the full attempt budget. Endpoints that already received the event are not sent it again.

| Code | HTTP | When |
| - | - | - |
| `not_found` | 404 | The endpoint or delivery is not in your app environment |
| `conflict` | 409 | The endpoint is not `active`, the delivery is not `failed` or `abandoned`, the endpoint is no longer subscribed to that event, or the delivery is being processed or already requeued |

Requeueing only helps if someone is watching, so alert on abandonment rather than on the first failure.

## Endpoint changes and pending deliveries

| Change with `PATCH /v2/console/webhooks/{webhookId}` | Effect on deliveries still pending or failed |
| - | - |
| `status: "disabled"` | Abandoned. The endpoint receives no new events until it is `active` again |
| `status: "archived"` | Abandoned. The endpoint can never be changed again |
| `subscribedEvents` narrowed | Deliveries for events no longer subscribed are abandoned |
| `status: "active"` | Refused with `409` if another active endpoint in the app environment uses the same URL |

## Related

<CardGroup cols={2}>
  <Card title="Webhooks overview" icon="bolt" href="/webhooks">
    Endpoints, delivery guarantees and headers.
  </Card>

  <Card title="Verifying signatures" icon="shield-check" href="/webhooks/verifying-signatures">
    Retries are re-signed; verify each one the same way.
  </Card>

  <Card title="Event catalog" icon="list" href="/webhooks/event-catalog">
    Every partner event and what it means.
  </Card>

  <Card title="Reconciliation" icon="scale-balanced" href="/payments/reconciliation">
    Sweep on your own schedule so a lost event never loses money.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.