> For the complete documentation index, see [llms.txt](https://developers.gallantreecapital.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.gallantreecapital.com/webhooks/delivery-and-retries.md).

# Delivery and retries

Deliveries fail. Networks flap, receivers restart, a caller's server sits at 100% CPU for a minute. The platform's job is to keep trying, up to a bound; your job is to make sure a delivery is easy to handle when it arrives.

## Timeouts

The platform waits **30 seconds** for a `2xx` response. Beyond 30 seconds, the delivery is treated as failed and enters the retry schedule — regardless of what your receiver eventually returns.

The corollary: do not do slow work inline. Enqueue the payload, return `2xx` immediately, and let a background worker do the actual processing.

## Retry schedule

Failed deliveries are retried on this exponentially-backing-off schedule:

| Attempt     | Wait after previous |
| ----------- | ------------------- |
| 1 (initial) | —                   |
| 2           | 1 minute            |
| 3           | 5 minutes           |
| 4           | 30 minutes          |
| 5           | 2 hours             |
| 6 (final)   | 12 hours            |

Total: up to **6 attempts** over roughly 14 hours 36 minutes. After the sixth attempt, the delivery is marked permanently failed and does not retry again.

## What counts as failure

* **Any non-2xx status code** — `4xx` and `5xx` both trigger retry. A persistent `4xx` from your receiver means your receiver is misconfigured; the platform will still retry per the schedule, but you're wasting your budget.
* **Timeout** (no response within 30 seconds).
* **TLS handshake failure** — an expired or untrusted certificate.
* **DNS resolution failure**.

## Subscription auto-disable

If a subscription's **failure count** — the count of consecutive failures — reaches **5**, the subscription is automatically **disabled**. No further deliveries are attempted until you re-enable it in the portal.

This is a safety measure — a receiver that's been down for a day is probably not coming back on its own, and continuing to hammer it wastes retry budget and delivery-log storage. When you fix the receiver, re-enable the subscription; new deliveries resume.

**A single successful delivery resets the failure count to zero.** You don't have to manually intervene after transient issues.

## The delivery log

The portal's **Delivery log** for a subscription shows every delivery attempt: the timestamp, the target URL, the HTTP status returned, the response body (truncated), and the delivery id (`X-Gallantree-Delivery`). It's your first stop when a receiver misses something.

Deliveries are retained for **30 days** in the log. Longer-term reliability metrics live in your own monitoring — hook into your receiver's success/failure count.

## Duplicate deliveries

The retry logic can produce duplicates. In particular:

* Your receiver returned `2xx` but the platform never received it (network partition after your response).
* Your receiver processed the delivery, crashed, then restarted, and the platform retries before it can recover.

**Deduplicate on `X-Gallantree-Delivery`.** The delivery id is stable across retries of the same underlying event. Record it in your receiver's dedupe store (Redis with a 24h TTL is a common choice) and no-op on the second occurrence.

## Ordering

Deliveries are **not guaranteed to arrive in order**. Two events on the same resource — say a `loan.updated` followed by a `loan.status_changed` — may reach your receiver out of order because the second one succeeded on the first attempt and the first one had to retry.

If order matters for your integration, fetch the current resource state from the API when you handle the event (using its `id` from the payload). The API is the source of truth; the webhook is a nudge to look.

## Runnable example — replaying from the delivery log

The portal has a **Replay** button on each delivery row; use it when you need to reproduce a specific delivery against your receiver during triage. Programmatically, the same is possible via:

```bash
curl -sS -X POST https://developers.gallantreecapital.com/api/v1/webhooks/deliveries/<delivery-id>/replay \
  -H "x-api-key: $GALLANTREE_API_KEY"
```

Replayed deliveries carry the same `X-Gallantree-Delivery` — your dedupe store will discard them if it's still holding the original.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers.gallantreecapital.com/webhooks/delivery-and-retries.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
