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

# Webhook Events

> Every event Anton emits, grouped by resource, with payload schemas.

Webhooks are HTTP POSTs Anton sends to a URL you control when something happens in your account — a payout moves through its lifecycle, a beneficiary is created, an FX exchange settles. Subscribe by calling `POST /v1/webhooks` with the URL and list of event types you want to receive.

Deliveries are **at-least-once**. You may occasionally receive the same event twice — deduplicate on the envelope `id`. Events for the same resource are dispatched in the order they occur, but network retries mean they can arrive out of order at your endpoint. Failed deliveries retry with exponential backoff (30 seconds, 2 minutes, 8 minutes, 32 minutes, then hourly up to **5 attempts total**) before being abandoned.

Every delivery is HMAC-signed — see [Webhook Signing](/api-reference/webhook-signing) for verification. Respond with any 2xx status within 30 seconds to acknowledge receipt.

## Delivery envelope

Every delivery has the same outer shape. `data` holds the resource-specific payload documented below.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9V",
  "type": "payout.completed",
  "created_at": "2026-04-15T14:30:00Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "status": "completed",
    "...": "resource-specific fields"
  }
}
```

Each delivery also carries these HTTP headers:

| Header                | Description                                                                    |
| --------------------- | ------------------------------------------------------------------------------ |
| `X-Webhook-ID`        | The envelope `id` — use for deduplication.                                     |
| `X-Webhook-Event`     | The event type, e.g. `payout.completed`.                                       |
| `X-Webhook-Timestamp` | Unix seconds at signing time. Used for verification.                           |
| `X-Webhook-Signature` | `v1={hex-hmac-sha256}`. See [Webhook Signing](/api-reference/webhook-signing). |
| `User-Agent`          | `AntonPayments-Webhook/1.0`                                                    |

## Event catalog

<CardGroup cols={2}>
  <Card title="Payout events" icon="money-bill-transfer" href="#payout-events">
    10 events covering the full payout lifecycle.
  </Card>

  <Card title="Beneficiary events" icon="user" href="#beneficiary-events">
    Beneficiary create, update, delete, and block.
  </Card>

  <Card title="Instrument events" icon="building-columns" href="#instrument-events">
    Payment instrument create, update, delete.
  </Card>

  <Card title="Batch events" icon="file-csv" href="#batch-events">
    Batch upload, completion, and failure.
  </Card>

  <Card title="FX events" icon="arrow-right-arrow-left" href="#fx-events">
    Quote locks and exchange execution.
  </Card>

  <Card title="Funding events" icon="money-bill-1" href="#funding-events">
    Incoming funds credited to your balance.
  </Card>

  <Card title="Compliance events" icon="shield-halved" href="#compliance-events">
    Screening hits that require manual review.
  </Card>

  <Card title="Test events" icon="flask" href="#test-events">
    Fired by the test-delivery endpoint.
  </Card>
</CardGroup>

## Payout events

| Event type                | Fires when                                                      |
| ------------------------- | --------------------------------------------------------------- |
| `payout.created`          | A payout is successfully created via `POST /v1/payouts`.        |
| `payout.approved`         | A pending payout is approved (maker-checker flow).              |
| `payout.processing`       | The payout has been submitted to the underlying rail provider.  |
| `payout.sent`             | The rail has confirmed funds are on the way to the beneficiary. |
| `payout.completed`        | The rail confirms funds have been delivered.                    |
| `payout.failed`           | Rail submission or execution failed terminally.                 |
| `payout.cancelled`        | The payout was cancelled before execution.                      |
| `payout.returned`         | The rail reported the funds were returned after sending.        |
| `payout.screening_failed` | Compliance screening blocked the payout.                        |
| `payout.velocity_blocked` | A risk/velocity rule blocked the payout at create time.         |

The payload for payout lifecycle events is the payout object. The exact shape depends on the event — `payout.created` includes the initial fields; `payout.sent`, `payout.completed`, and `payout.failed` include rail identifiers and timestamps as they become available. `payout.screening_failed` and `payout.velocity_blocked` use a compact shape with just `id`, `status`, and `reason`.

<Note>
  When a real-time risk score is available on the payout (available via the Anton risk engine), the lifecycle event payloads include a `risk` object summarising the score and contributing factors.
</Note>

### `payout.created`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9V",
  "type": "payout.created",
  "created_at": "2026-04-15T14:30:00Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "pending_screening",
    "source_amount": "500.00",
    "source_currency": "USD",
    "dest_amount": "458.25",
    "dest_currency": "EUR",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "instrument_id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2",
    "reference": "Invoice #1042",
    "created_at": "2026-04-15T14:30:00Z"
  }
}
```

### `payout.processing`, `payout.sent`, `payout.completed`, `payout.failed`, `payout.returned`

These share the same lifecycle payload shape. `rail_provider`, `rail_reference`, `completed_at`, and `failed_at` are populated when the rail reports them.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9W",
  "type": "payout.completed",
  "created_at": "2026-04-15T14:45:12Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "completed",
    "source_amount": "500.00",
    "source_currency": "USD",
    "dest_amount": "458.25",
    "dest_currency": "EUR",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "instrument_id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2",
    "reference": "Invoice #1042",
    "rail_provider": "openpayd",
    "rail_reference": "OP-2026-04-15-778812",
    "created_at": "2026-04-15T14:30:00Z",
    "updated_at": "2026-04-15T14:45:12Z",
    "completed_at": "2026-04-15T14:45:12Z"
  }
}
```

### `payout.screening_failed`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9X",
  "type": "payout.screening_failed",
  "created_at": "2026-04-15T14:30:03Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "screening_failed",
    "reason": "compliance screening flagged this payout"
  }
}
```

### `payout.velocity_blocked`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9Y",
  "type": "payout.velocity_blocked",
  "created_at": "2026-04-15T14:30:01Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "status": "velocity_blocked",
    "reason": "payout blocked by risk policy"
  }
}
```

### `payout.approved`

Fires when a payout transitions to `approved` — either because compliance screening cleared it or an ops reviewer approved the manual review case it was queued in. Arrives before `payout.processing` for the same payout.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9W",
  "type": "payout.approved",
  "created_at": "2026-04-15T14:30:05Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "approved",
    "source_amount": "500.00",
    "source_currency": "USD",
    "dest_amount": "458.25",
    "dest_currency": "EUR",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "instrument_id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2",
    "reference": "Invoice #1042",
    "created_at": "2026-04-15T14:30:00Z",
    "updated_at": "2026-04-15T14:30:05Z"
  }
}
```

### `payout.cancelled`

Fires when a payout is cancelled before being submitted to the rail. The payload includes the `previous_status` at the time of cancellation and the `released_amount` that was returned to the merchant's available balance.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8U9V",
  "type": "payout.cancelled",
  "created_at": "2026-04-15T14:31:00Z",
  "data": {
    "id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "cancelled",
    "previous_status": "created",
    "released_amount": "500.00",
    "source_amount": "500.00",
    "source_currency": "USD",
    "dest_amount": "458.25",
    "dest_currency": "EUR",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "instrument_id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2",
    "reference": "Invoice #1042",
    "created_at": "2026-04-15T14:30:00Z",
    "updated_at": "2026-04-15T14:31:00Z"
  }
}
```

## Beneficiary events

| Event type            | Fires when                                                           |
| --------------------- | -------------------------------------------------------------------- |
| `beneficiary.created` | A beneficiary is created via `POST /v1/beneficiaries`.               |
| `beneficiary.updated` | A beneficiary is edited, archived, restored, or has its PII updated. |
| `beneficiary.deleted` | A beneficiary is deleted. Payload contains only the id.              |
| `beneficiary.blocked` | A beneficiary is blocked (e.g. by compliance).                       |

The payload's `data` field contains the beneficiary resource. Sensitive fields (BT token references, fingerprints, deletion timestamps) are never included.

### `beneficiary.created`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UB1",
  "type": "beneficiary.created",
  "created_at": "2026-04-15T12:00:00Z",
  "data": {
    "id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "type": "individual",
    "status": "active",
    "display_name": "Jordan Rivera",
    "country": "DE",
    "external_ref": "crm-id-8814",
    "metadata": {
      "cost_center": "engineering"
    },
    "created_at": "2026-04-15T12:00:00Z",
    "updated_at": "2026-04-15T12:00:00Z"
  }
}
```

### `beneficiary.updated`, `beneficiary.blocked`

Same shape as `beneficiary.created` with the event type and resource state reflecting the new values.

### `beneficiary.deleted`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UB4",
  "type": "beneficiary.deleted",
  "created_at": "2026-04-15T12:30:00Z",
  "data": {
    "id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1"
  }
}
```

## Instrument events

| Event type           | Fires when                                                                                |
| -------------------- | ----------------------------------------------------------------------------------------- |
| `instrument.created` | An instrument is attached to a beneficiary via `POST /v1/beneficiaries/{id}/instruments`. |
| `instrument.updated` | An instrument is edited.                                                                  |
| `instrument.deleted` | An instrument is deleted. Payload contains only the id.                                   |

### `instrument.created`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UC1",
  "type": "instrument.created",
  "created_at": "2026-04-15T12:05:00Z",
  "data": {
    "id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "method": "iban",
    "currency": "EUR",
    "country": "DE",
    "label": "Commerzbank EUR",
    "display_last4": "0194",
    "display_bank": "Commerzbank",
    "masked_account": "DE89 **** **** **** **** 0194",
    "status": "active",
    "is_default": true,
    "created_at": "2026-04-15T12:05:00Z",
    "updated_at": "2026-04-15T12:05:00Z"
  }
}
```

Credential tokens and fingerprints are never included — raw instrument credentials live only in the Basis Theory vault.

### `instrument.updated`

Same shape as `instrument.created` with the new values reflected.

### `instrument.deleted`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UC4",
  "type": "instrument.deleted",
  "created_at": "2026-04-15T12:45:00Z",
  "data": {
    "id": "ins_01HX8Z9K0M2N3P4Q5R6S7T8UA2"
  }
}
```

## Batch events

| Event type        | Fires when                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------- |
| `batch.uploaded`  | A CSV or XLSX batch file is accepted by `POST /v1/batches`. Fires before validation begins. |
| `batch.completed` | A batch finished processing with at least one successful row (includes partial success).    |
| `batch.failed`    | A batch failed terminally — no rows could be processed, or every row failed.                |

### `batch.uploaded`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UE2",
  "type": "batch.uploaded",
  "created_at": "2026-04-15T15:10:00Z",
  "data": {
    "id": "batch_01HX8Z9K0M2N3P4Q5R6S7T8UF1",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "uploaded",
    "file_name": "payouts-2026-04-15.csv",
    "file_size": 48221,
    "format": "csv",
    "uploaded_at": "2026-04-15T15:10:00Z",
    "uploaded_by": "usr_01HX8Z9K0M2N3P4Q5R6S7T8UG1"
  }
}
```

### `batch.completed`

Fires when processing finishes with `status` of `completed` (all rows succeeded) or `partial` (some succeeded, some failed). Branch on the payload `status` field to distinguish full from partial success.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UE3",
  "type": "batch.completed",
  "created_at": "2026-04-15T15:18:00Z",
  "data": {
    "id": "batch_01HX8Z9K0M2N3P4Q5R6S7T8UF1",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "completed",
    "file_name": "payouts-2026-04-15.csv",
    "format": "csv",
    "total_rows": 120,
    "processed_rows": 120,
    "valid_rows": 120,
    "invalid_rows": 0,
    "skipped_rows": 0,
    "created_at": "2026-04-15T15:10:00Z",
    "completed_at": "2026-04-15T15:18:00Z"
  }
}
```

### `batch.failed`

Fires when a batch finishes with `status = failed` — either the file could not be processed at all (download/parse failure, empty file) or every row failed during processing. The payload includes an `errors` array describing the row-level failures when available.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UE4",
  "type": "batch.failed",
  "created_at": "2026-04-15T15:18:00Z",
  "data": {
    "id": "batch_01HX8Z9K0M2N3P4Q5R6S7T8UF1",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "status": "failed",
    "file_name": "payouts-2026-04-15.csv",
    "format": "csv",
    "total_rows": 120,
    "processed_rows": 0,
    "valid_rows": 0,
    "invalid_rows": 120,
    "skipped_rows": 0,
    "created_at": "2026-04-15T15:10:00Z",
    "completed_at": "2026-04-15T15:18:00Z",
    "errors": [
      { "row": 2, "field": "currency", "message": "unsupported currency: XYZ" }
    ]
  }
}
```

## FX events

| Event type              | Fires when                                                                          |
| ----------------------- | ----------------------------------------------------------------------------------- |
| `fx.quote.created`      | An FX quote is locked (binding commitment). Indicative quotes do not fire an event. |
| `fx.exchange.created`   | An FX exchange is executed and is pending settlement.                               |
| `fx.exchange.completed` | The exchange has settled.                                                           |
| `fx.exchange.failed`    | The exchange failed to settle, or was stuck in pending beyond the retry budget.     |

### `fx.quote.created`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UD1",
  "type": "fx.quote.created",
  "created_at": "2026-04-15T13:00:00Z",
  "data": {
    "id": "fxq_01HX8Z9K0M2N3P4Q5R6S7T8UD2",
    "sell_currency": "GBP",
    "buy_currency": "EUR",
    "sell_amount": "1000.00",
    "buy_amount": "1173.40",
    "customer_rate": "1.17340",
    "locked": true,
    "expires_at": "2026-04-15T13:05:00Z"
  }
}
```

### `fx.exchange.created`, `fx.exchange.completed`, `fx.exchange.failed`

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UD3",
  "type": "fx.exchange.completed",
  "created_at": "2026-04-15T13:01:12Z",
  "data": {
    "quote_id": "fxq_01HX8Z9K0M2N3P4Q5R6S7T8UD2",
    "sell_currency": "GBP",
    "buy_currency": "EUR",
    "sell_amount": "1000.00",
    "buy_amount": "1173.40",
    "mid_market_rate": "1.17450",
    "customer_rate": "1.17340",
    "provider": "openpayd",
    "provider_ref": "OP-FX-2026-04-15-41102",
    "status": "completed"
  }
}
```

A `fx.exchange.failed` event fired by the status-polling worker (for exchanges that get stuck in `pending`) uses a compact payload:

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UD4",
  "type": "fx.exchange.failed",
  "created_at": "2026-04-15T13:11:00Z",
  "data": {
    "id": "fxe_01HX8Z9K0M2N3P4Q5R6S7T8UD5",
    "sell_currency": "GBP",
    "buy_currency": "EUR",
    "sell_amount": "1000.00",
    "buy_amount": "1173.40",
    "status": "failed",
    "reason": "exchange stuck in pending state"
  }
}
```

## Funding events

### `funding.credit`

Fires when incoming funds are received on a merchant account and credited to the internal balance. The payload carries the masked sender name only — Anton never forwards raw counterparty PII to webhook subscribers.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UF2",
  "type": "funding.credit",
  "created_at": "2026-04-15T16:00:00Z",
  "data": {
    "id": "opd_01HX8Z9K0M2N3P4Q5R6S7T8UF3",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "amount": "10000.00",
    "currency": "USD",
    "sender_name": "J*** S****",
    "account_id": "opa_01HX8Z9K0M2N3P4Q5R6S7T8UF4",
    "reference": "Invoice top-up",
    "source_provider": "openpayd",
    "credited_at": "2026-04-15T16:00:00Z"
  }
}
```

## Compliance events

### `screening.hit`

Fires when compliance screening returns a possible match that requires manual review. The payload intentionally omits the matched sanctions-list entry content — Anton treats that information as compliance-sensitive and does not surface match details to merchants to avoid tipping-off risk. Subscribers should treat this event as a signal that a review case is open for the payout; the payout itself transitions to `manual_review`.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UF5",
  "type": "screening.hit",
  "created_at": "2026-04-15T14:30:04Z",
  "data": {
    "payout_id": "pay_01HX8Z9K0M2N3P4Q5R6S7T8UAB",
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "beneficiary_id": "ben_01HX8Z9K0M2N3P4Q5R6S7T8UA1",
    "screening_id": "scr_01HX8Z9K0M2N3P4Q5R6S7T8UF6",
    "highest_score": 0.72,
    "review_id": "rev_01HX8Z9K0M2N3P4Q5R6S7T8UF7",
    "status": "manual_review"
  }
}
```

## Balance events

### `balance.low`

Fires when a merchant's available balance drops below a configured low-balance threshold. Thresholds are configured per `(merchant, currency)` pair — one threshold per pair — in the merchant dashboard. The event uses **latching** semantics to prevent spam while the balance oscillates around the threshold:

* Fires once when the available balance transitions from at-or-above the threshold to below it.
* Does not fire again while the balance remains below the threshold.
* Re-arms automatically when the balance recovers to at-or-above the threshold. No recovery event is emitted.
* The next dip below the threshold fires again.

Delivery is best-effort and runs asynchronously from the balance mutation. A webhook failure never blocks the underlying payout, funding credit, or FX exchange.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UG1",
  "type": "balance.low",
  "created_at": "2026-04-15T17:22:00Z",
  "data": {
    "merchant_id": "mer_01HX8Z9K0M2N3P4Q5R6S7T8UZZ",
    "currency": "USD",
    "balance_amount": "87.42",
    "threshold_amount": "100.00",
    "latched_at": "2026-04-15T17:22:00Z"
  }
}
```

## Test events

### `test`

Fired by `POST /v1/webhooks/{id}/test` to help you verify your endpoint is reachable and your signature verification works. The payload is a synthetic example with hard-coded identifiers — do not persist or act on it.

```json theme={}
{
  "id": "evt_01HX8Z9K0M2N3P4Q5R6S7T8UE1",
  "type": "test",
  "created_at": "2026-04-15T15:00:00Z",
  "data": {
    "test": true,
    "event_type": "test",
    "object": "payout",
    "id": "pay_test000000000000001",
    "status": "completed",
    "source_amount": "100.00",
    "source_currency": "USD",
    "dest_amount": "85.00",
    "dest_currency": "EUR",
    "created_at": "2026-04-15T15:00:00Z"
  }
}
```

## Subscribing to events

Create a subscription with the list of event types you want to receive. The signing `secret` is returned once, at creation time — store it securely.

<Note>
  The `url` must use `https://` and must be publicly reachable from the internet. Anton rejects plaintext `http://` endpoints, loopback addresses, RFC1918 private ranges, `.local` / `.internal` hostnames, and cloud metadata endpoints — these are refused with `webhook_url_not_https` or `webhook_url_private_address`. Plaintext `http://` is only accepted against a local development API. See [Errors → Webhook subscriptions](/api-reference/errors#webhook-subscriptions).
</Note>

```bash theme={}
curl https://api.antonpayments.dev/v1/webhooks \
  -X POST \
  -H "Authorization: DPoP $ANTON_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/anton",
    "events": ["payout.created", "payout.completed", "payout.failed"]
  }'
```

Inspect past deliveries with `GET /v1/webhooks/events`, `GET /v1/webhooks/events/{id}`, and `GET /v1/webhooks/events/{id}/deliveries`. See [Webhook Signing](/api-reference/webhook-signing) for verification.
