# Handle Deliveries

This document describes how webhook events are delivered, acknowledged, retried, and how consumers should process and order them.

## Overview

Webhook events are delivered using an **at-least-once delivery model**.

This means:

> **Each event is guaranteed to be delivered one or more times.**
> Duplicate deliveries are possible and expected.

Webhook consumers **must implement idempotent processing**.

## Delivery Model

- **Delivery guarantee:** At least once
- **Duplicates:** Possible
- **Ordering:** Not guaranteed at delivery time

## Delivery Method

- **Protocol:** HTTPS only
- **HTTP Method:** `POST`
- **Content-Type:** `application/json`
- **Encoding:** UTF-8

## Delivery Headers

Each delivery includes the following headers:

| Header             | Description                                                                            |
| ------------------ | -------------------------------------------------------------------------------------- |
| `X-Agc-Version`    | Webhook payload version                                                                |
| `X-Agc-Tenant-Id`  | ID of the tenant that triggered the event                                              |
| `X-Agc-Webhook-Id` | ID of the configured webhook                                                           |
| `X-Agc-Attempt-Id` | Unique identifier for **this delivery attempt**                                        |
| `X-Agc-Event-Id`   | Unique event identifier (stable across retries, use for idempotency)                   |
| `X-Agc-Event-Type` | Name of the event type (e.g. `pid.completed`)                                          |
| `X-Agc-Timestamp`  | ISO-8601 timestamp indicating **when this delivery attempt was sent**                  |
| `X-Agc-Signature`  | HMAC signature used to verify authenticity (**only present if a secret is configured** |

## Delivery Payload

The webhook request body uses the following structure:

```json
{
  "version": "1.0",
  "tenant_id": "tenant_abc",
  "event_id": "evt_123456789",
  "event_type": "pid.completed",
  "event_timestamp": "2026-01-22T06:40:00.000Z",
  "data": {}
}
```

### Payload Field Semantics

| Field             | Description                                     |
| ----------------- | ----------------------------------------------- |
| `version`         | Webhook payload version                         |
| `tenant_id`       | ID of the tenant that triggered the event       |
| `event_id`        | Unique event identifier (stable across retries) |
| `event_type`      | Event type name                                 |
| `event_timestamp` | When the event occurred in the platform         |
| `data`            | Event-specific payload                          |

---

## Success & Failure Criteria

### Successful Delivery

Any HTTP response with a status code in the range:

```
200–399
```

is considered a **successful delivery**, and **no further retries will be made**.

### Retryable Failures

The following status codes **will trigger retries**:

- `408` – Request Timeout
- `429` – Too Many Requests
- `500–599` – Server errors

Retries are also triggered for:

- Network errors
- TLS failures
- DNS failures
- Connection timeouts

### Non-Retryable Failures

The following status codes **will NOT be retried**:

- `400` – Bad Request
- `401` – Unauthorized
- `403` – Forbidden
- `404` – Not Found
- `410` – Gone

When these responses are received, the delivery is considered **final**.

---

## Retry Behavior

### Retry Strategy

- Retries use **exponential backoff**
- Retries continue for **up to 24 hours**
- Retry intervals are not guaranteed and may vary

### Retry Characteristics

- `event_id` remains **constant** across all retries
- `attempt_id` is **unique** for every delivery attempt
- The same payload is resent for each retry

---

## Idempotency (Required)

Due to at-least-once delivery semantics, webhook consumers **must implement idempotent processing**.

### Idempotency Key

Use:

```
event_id
```

### Recommended Processing Pattern

```text
if event_id already processed:
    return success
else:
    process event
    store event_id
```

Failing to implement idempotency may result in duplicate side effects.

---

## Timeout Expectations

Webhook endpoints should:

- Respond within a few seconds
- Avoid long-running synchronous processing
- Offload heavy work to background jobs or queues

Slow responses increase retry likelihood.

---

## Security Considerations

- Use HTTPS
- Validate HMAC signatures when present
- Validate timestamps (`X-Agc-Timestamp`) to prevent replay attacks
- Reject malformed payloads

See [**Validating Deliveries**](https://apidocs.agilitycredit.com/webhooks/validating-deliveries) for details.

---

## Logging & Observability

Strongly recommended logging fields:

- `event_id`
- `event_type`
- `attempt_id`
- `event_timestamp`
- `X-Agc-Timestamp`
- HTTP response codes
- Signature verification failures

Source: https://apidocs.agilitycredit.com/webhooks/handle-deliveries
