# Retries and failures

URL: https://submitator.com/docs/webhooks/retries-and-failures

> Answer 2xx within 10 seconds. A delivery is tried up to 7 times over about 33 hours, and an endpoint that fails for 72 hours is disabled until you enable it again.

## What counts as delivered

An attempt succeeds when your endpoint answers any 2xx status within 10 seconds, with 5 seconds to connect. Any other status, a timeout, a failed connection, a TLS error or a redirect is a failed attempt. Deliveries do not follow redirects, so register the final URL.

Answer first and work later: store the event, queue what it starts and return 204. Work that takes longer than 10 seconds makes a delivered event look failed, and it comes again.

## The retry schedule

A failed attempt is tried again after a wait that grows each time. Each wait counts from the attempt before it.

| Attempt | When |
| --- | --- |
| 1 | Right after the event is recorded. |
| 2 | 1 minute after attempt 1. |
| 3 | 5 minutes after attempt 2. |
| 4 | 30 minutes after attempt 3. |
| 5 | 2 hours after attempt 4. |
| 6 | 6 hours after attempt 5. |
| 7 | 24 hours after attempt 6. |

When attempt 7 fails, about 33 hours after the event, the delivery is `failed`. The event stays in `GET /events` for 90 days, so you can still read it from the feed.

## See your deliveries

`GET /webhook_endpoints/{webhook_endpoint_id}/deliveries` lists the deliveries to one endpoint, newest first. Filter by `status`: `pending` while attempts remain, `succeeded` or `failed` once they end.

```bash
curl "https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT/deliveries?status=pending" \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
```

```js
const res = await fetch(
  "https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT/deliveries?status=pending",
  { headers: { Authorization: `Bearer ${process.env.SUBMITATOR_API_KEY}` } },
);
console.log(await res.json());
```

```python
import os
import requests

res = requests.get(
    "https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT/deliveries",
    headers={"Authorization": f"Bearer {os.environ['SUBMITATOR_API_KEY']}"},
    params={"status": "pending"},
)
print(res.json())
```

Response 200:

```json
{
  "object": "list",
  "data": [
    {
      "object": "webhook_delivery",
      "id": "wd_3Cr8Kx2Wq7Lt4Vb9Nz1HmP",
      "livemode": false,
      "endpoint": "we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT",
      "event": "evt_6Lr3Kx8Wq2Nt7Vb1Rz5MpA",
      "event_type": "listing.action_required",
      "status": "pending",
      "attempts": 5,
      "next_attempt_at": "2026-10-08T17:49:31Z",
      "last_attempt": {
        "attempted_at": "2026-10-08T11:49:31Z",
        "http_status": null,
        "duration_ms": 10000,
        "error": "timeout"
      },
      "created_at": "2026-10-08T09:13:30Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

`last_attempt.error` says why an attempt failed without a 2xx: `timeout`, `connection_failed`, `tls_failed`, `redirect` or `blocked_address`, when the host is not public. It is `null` when your endpoint answered, and `http_status` then holds the status it sent.

The endpoint itself sums up the last 7 days in `delivery_summary`: attempts, successes, and the time and status of the last attempt.

## Send a delivery again

`POST /webhook_deliveries/{webhook_delivery_id}/retry` sends a delivery again now, whatever its status. Use it once you have fixed your endpoint, for the deliveries that failed meanwhile.

```bash
curl -X POST https://api.submitator.com/v1/webhook_deliveries/wd_3Cr8Kx2Wq7Lt4Vb9Nz1HmP/retry \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
```

The endpoint must be enabled: a retry to a disabled endpoint returns 422 `validation_failed`.

## When an endpoint is disabled

An endpoint with no successful delivery for 72 hours and at least 10 failed events is disabled: `enabled` becomes `false` and `disabled_reason` becomes `failing`. We email you when it happens.

A disabled endpoint gets no deliveries, but events go on being recorded in `GET /events`. Fix the endpoint, enable it again, and read the feed from your last cursor to catch up on what it missed.

```bash
curl -X PATCH https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'
```

```js
const res = await fetch("https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.SUBMITATOR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ enabled: true }),
});
console.log(await res.json());
```

```python
import os
import requests

res = requests.patch(
    "https://api.submitator.com/v1/webhook_endpoints/we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT",
    headers={"Authorization": f"Bearer {os.environ['SUBMITATOR_API_KEY']}"},
    json={"enabled": True},
)
print(res.json())
```

Response 200 (some fields left out):

```json
{
  "object": "webhook_endpoint",
  "id": "we_4Xk9Lm2Qt7Wr3Nb8Vz5HcT",
  "url": "https://hooks.northlight.test/listings",
  "enabled": true,
  "disabled_reason": null
}
```

You can disable an endpoint yourself with `enabled` `false`: `disabled_reason` is then `requested`. Deleting an endpoint drops the deliveries still waiting for it, and its events stay in the feed.

## What does not count

Deliveries to your endpoints do not count toward your [rate limit](https://submitator.com/docs/rate-limits). Your calls to read deliveries, retry them or change an endpoint do.

## What can go wrong

| Code | Status | Fix |
| --- | --- | --- |
| [`endpoint_url_invalid`](https://submitator.com/docs/errors#endpoint_url_invalid) | 422 | The URL is not a public `https` URL, or holds a user name or password. |
| [`validation_failed`](https://submitator.com/docs/errors#validation_failed) | 422 | A retry to a disabled endpoint, or an `events` value that is not a type or a group. `errors` says which. |
| [`quota_exceeded`](https://submitator.com/docs/errors#quota_exceeded) | 422 | Before your first purchase, test mode allows 3 webhook endpoints. Delete one first. |
| [`not_found`](https://submitator.com/docs/errors#not_found) | 404 | The endpoint or delivery belongs to the other mode, or was deleted. |

## Next steps

- [Signatures](https://submitator.com/docs/webhooks/signatures): check that a delivery came from us.
- [Event types](https://submitator.com/docs/webhooks/event-types): every event and what its `data` holds.
