# Retries and testing
URL: https://musicapi.com/docs/webhooks/retries-and-testing.md

> What counts as a delivered webhook, the retry schedule of 5 attempts, why a redirect fails at once, and how the test event works.

Product: MusicAPI Managed Transfers. Section: Webhooks and alerts.

## What counts as delivered

We wait **10 seconds** for your endpoint.

| Your answer                                       | Result                                             |
| ------------------------------------------------- | -------------------------------------------------- |
| Any `2xx`                                         | Delivered. No more attempts.                       |
| A `3xx` redirect                                  | Failed, with no retry. We never follow a redirect. |
| Any other status, a timeout, a refused connection | A failed attempt. We retry.                        |

Only the status counts. We do not read the body of your answer.

## The retry schedule

A delivery gets at most **5 attempts** in total. After a failed attempt, the next one waits `2^attempt × 30 seconds`:

| Attempt | Waits before it |
| ------- | --------------- |
| 1       | Sent at once    |
| 2       | 60 seconds      |
| 3       | 120 seconds     |
| 4       | 240 seconds     |
| 5       | 480 seconds     |

After the fifth failed attempt, the delivery is marked failed and left alone. So a delivery has about 15 minutes to get through.

A delivery also fails at once, with no retry, when:

- you turned the endpoint off after the event was queued;
- the endpoint's host now resolves to a private or internal address.

Make your handler idempotent: a retry sends the identical body, with the same `id`.

## The delivery log

Under **API & webhooks**, each endpoint shows its delivery log: the event, the status, the attempts, the status code and the response time of your server. The [MCP server](https://musicapi.com/docs/ai/mcp-server.md) reads the same log with `list_webhook_deliveries`.

## Send a test event

The **Send test event** button queues a real `transfer.completed` delivery through the real path: the same signature, the same log.

```json
{
  "id": "evt_5c2e8f1a-7b3d-4e9a-a1c6-0d4f2b8e6a71",
  "event": "transfer.completed",
  "createdAt": "2026-10-16T10:15:42.101Z",
  "data": {
    "test": true,
    "transferUuid": "00000000-0000-0000-0000-000000000000",
    "status": "completed",
    "totalItems": 100,
    "processedItems": 100,
    "matchedItems": 97,
    "unmatchedItems": 3,
    "skippedItems": 0,
    "failedItems": 0,
    "resultsUrl": "https://api.musicapi.com/public/transfers/00000000-0000-0000-0000-000000000000/unmatched",
    "endUserId": null,
    "batch": null
  }
}
```

The data is a fixed sample with `"test": true` and a zeroed transfer uuid. Ignore deliveries with `test: true` in production logic.

Two rules differ from a real delivery:

- A test delivery gets **one** attempt, not five.
- One test per endpoint per minute. A second press within the minute answers `429`.

## Test on your laptop

Our deliveries need a public `https` URL. Expose your local server with a tunnel (for example `ngrok http 3000` or `cloudflared tunnel`), add the tunnel URL as an endpoint, and press **Send test event**.
