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

# Webhook Events

> Outbound case events sent to clientWebhookUrl.

## Delivery Contract

When `clientWebhookUrl` is present on a case, Offload sends:

```http theme={null}
POST <clientWebhookUrl>
content-type: application/json
```

The current implementation:

* sends only one request per event
* treats any `2xx` as success
* does not sign events
* does not retry failures

## Common Envelope

```json theme={null}
{
  "event_id": "evt_4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f_case_completed_1774131900000",
  "event_type": "case.completed",
  "timestamp": 1774131900000,
  "data": {}
}
```

## Event Types

### `case.completed`

Sent when the case becomes `COMPLETED`.

```json theme={null}
{
  "event_type": "case.completed",
  "data": {
    "caseId": "4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f",
    "clientReferenceId": "vendor_2048",
    "status": "COMPLETED",
    "channel": "EMAIL",
    "attemptCount": 2,
    "attachments": [
      {
        "attachmentId": "63016fee-124b-46d8-a3e5-616f187a6c41",
        "name": "vendor-w9.pdf",
        "size": 904860,
        "type": "application/pdf",
        "messageId": "<message-id@example.com>",
        "inboxId": "dev-test@agentmail.to",
        "receivedAt": 1774131900000
      }
    ],
    "resultStatus": "goal_achieved",
    "result": {
      "signedW9Received": true,
      "receivedAt": "2026-03-21T15:42:00Z"
    },
    "metadata": {
      "vendorId": "vendor_2048"
    }
  }
}
```

### `case.failed`

Sent when the case becomes `FAILED`.

```json theme={null}
{
  "event_type": "case.failed",
  "data": {
    "caseId": "4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f",
    "status": "FAILED",
    "channel": "EMAIL",
    "attemptCount": 3,
    "attachments": [],
    "resultStatus": "max_retries",
    "failureReason": null
  }
}
```

Known `resultStatus` values include:

* `max_retries`
* `failed_rejected`
* `BOUNCED`
* `COMPLAINED`
* `REJECTED`

### `case.input_needed`

Sent when the worker pauses the case and needs your input.

```json theme={null}
{
  "event_type": "case.input_needed",
  "data": {
    "caseId": "4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f",
    "status": "INPUT_NEEDED",
    "channel": "EMAIL",
    "attemptCount": 1,
    "attachments": [],
    "inputRequest": "The vendor asked whether a signed PDF is acceptable. How should I respond?",
    "inputRequestId": "c8c6a7ca-a913-4d13-8cd8-5887efb0531c",
    "inputRequestStatus": "PENDING",
    "inputRequestedAt": 1774131900300
  }
}
```

## Fields In `data`

| Field                | Present when                        |
| -------------------- | ----------------------------------- |
| `caseId`             | Always                              |
| `clientReferenceId`  | When provided at case creation      |
| `status`             | Always                              |
| `channel`            | Always                              |
| `attemptCount`       | Always                              |
| `attachments`        | Always                              |
| `result`             | Usually on `case.completed`         |
| `metadata`           | When provided at case creation      |
| `resultStatus`       | Terminal and workflow outcome cases |
| `failureReason`      | Some `case.failed` events           |
| `inputRequest`       | `case.input_needed`                 |
| `inputRequestId`     | `case.input_needed`                 |
| `inputRequestStatus` | `case.input_needed`                 |
| `inputRequestedAt`   | `case.input_needed`                 |

## Handling Advice

* Deduplicate on `event_id`.
* Persist the full payload.
* Return a fast `2xx`.
* Use the attachments endpoint when you need a fresh file URL.
* Poll important cases for reconciliation because failed webhooks are not retried.
