> ## 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.

# Handle Webhooks

> Subscribe to events, verify signatures, and build a webhook consumer you can rely on.

Webhooks are how Anton tells your system that something happened without you having to ask. A payout settles, a beneficiary is blocked, an RFI is raised — Anton signs a JSON event and POSTs it to an HTTPS URL you control.

This guide covers the integrator's end-to-end: how to subscribe, how to verify signatures, how to handle retries, and what patterns your receiver should follow.

## When to use webhooks (vs polling)

**Always use webhooks in production.** Polling has three problems:

* Wastes rate-limit budget — `GET /v1/payouts/{id}` in a loop quickly hits the 1,000 req/min per-merchant ceiling.
* Introduces latency — you learn about state changes on your polling interval, not when they happen.
* Misses intermediate transitions — by the time you poll, a payout may have already moved through two states.

Polling is fine during development when you don't yet have a public HTTPS endpoint. For production, subscribe.

## Creating a subscription

```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://your-app.example.com/webhooks/anton",
    "events": [
      "payout.completed",
      "payout.failed",
      "payout.returned",
      "payout.screening_failed",
      "beneficiary.blocked",
      "rfi.created"
    ]
  }'
```

The URL must be:

* **HTTPS** — `http://` is rejected in all non-development environments.
* **Publicly reachable** — private IP ranges, loopback, and cloud metadata endpoints (e.g. `169.254.169.254`) are rejected server-side.

The response includes the subscription `id` (`wbh_...`) and a one-time `secret` (`whsec_...`). **Store the secret in your secrets manager immediately** — Anton doesn't retain the plaintext, and you cannot retrieve it later without calling `GET /v1/webhooks/{id}/secret` (which you can, but rotating is cleaner).

Use `["*"]` as the `events` array to subscribe to everything.

## Signing and verification

Every delivery carries HMAC-SHA256 headers:

* `X-Webhook-Signature: v1={hex-hmac}`
* `X-Webhook-Timestamp: {unix-seconds}`
* `X-Webhook-ID: {event-id}` — use for deduplication
* `X-Webhook-Event: {event-type}`
* `User-Agent: AntonPayments-Webhook/1.0`

The signed string is `{timestamp}.{raw-body}`. Verify every delivery in constant time.

See [Webhook Signing](/api-reference/webhook-signing) for copy-paste verification code in bash, JavaScript, PHP, and Go. Treat unverified deliveries as untrusted HTTP requests — do not act on the payload until the signature checks out.

<Warning>
  Parse the **raw request body**, not parsed-and-reserialized JSON. Re-encoding changes byte ordering and breaks verification.
</Warning>

## Delivery semantics

* **At-least-once.** Subscribers may occasionally receive the same event twice. Deduplicate on the envelope `id`.
* **Ordering** — events for the same resource dispatch in the order they occur, but network retries can re-order arrivals at your endpoint. Don't assume order; reconcile state from the payload.
* **Retries** — failed deliveries retry with exponential backoff: 30s, 2m, 8m, 32m, hourly. Up to 5 total attempts. After 5 failures, the delivery is abandoned.
* **Timeout** — Anton waits up to 30 seconds for your `2xx` response. Return 2xx fast and do heavy work asynchronously (enqueue, then respond).
* **Redirects** — not followed. Your URL must respond directly.

A delivery is successful when your endpoint returns any `2xx` status code. Non-2xx responses (including `3xx`) count as failure and trigger retries.

## The events you probably want

Core payout lifecycle:

* `payout.completed` — funds delivered
* `payout.failed` — rail rejected or failed settlement
* `payout.returned` — funds returned after send
* `payout.screening_failed` — sanctions or compliance block
* `payout.velocity_blocked` — velocity rule rejected

Beneficiary lifecycle:

* `beneficiary.created`
* `beneficiary.updated` — edit, PII update, archive, or restore
* `beneficiary.blocked` — compliance action

Compliance workflow:

* `rfi.created` — Anton raised a Request for Information
* `screening.hit` — payout held pending screening review

FX:

* `fx.exchange.completed`, `fx.exchange.failed`

Funding:

* `funding.credit` — deposit landed in your balance

The full catalog (27 event types) with payload schemas is at [Webhook Events](/api-reference/webhook-events).

## Receiver patterns

A production-ready webhook receiver does four things in order:

<Steps>
  <Step title="Enqueue fast">
    Read the raw body, enqueue it for async processing, return `200` immediately. Don't block on database writes or downstream API calls inside the request handler — Anton's 30-second timeout is generous but chains of dependent work can exceed it.
  </Step>

  <Step title="Verify the signature">
    Before the enqueued worker trusts the payload, verify the signature using the stored secret. Reject anything older than 5 minutes (replay protection).
  </Step>

  <Step title="Deduplicate">
    Check the envelope `id` against a recent-events store (Redis SET with TTL works). Skip if already seen. At-least-once delivery means this IS going to happen.
  </Step>

  <Step title="Apply idempotently">
    Design your event handlers so applying the same event twice converges on the same state. Use the resource's `status` as the source of truth — `payout.completed` twice should move a payout to `completed` once.
  </Step>
</Steps>

## Debugging delivery failures

`GET /v1/webhooks/events/{id}/deliveries` returns every delivery attempt Anton made for a given event — the exact request headers sent, your response status, response body (truncated), transport errors, duration, and retry schedule.

When a delivery is failing, this is where you look:

```bash theme={}
curl "https://api.antonpayments.dev/v1/webhooks/events/$EVENT_ID/deliveries" \
  -H "Authorization: DPoP $ANTON_ACCESS_TOKEN"
```

Common failure modes:

* **Signature mismatch (400 from your endpoint)** — you're verifying against a parsed body instead of the raw bytes, or the secret rotated and you didn't update.
* **Connection timeout** — your endpoint took longer than 30s to respond. Move work off the request path.
* **5xx from your endpoint** — application error; look at your own logs.
* **Certificate errors** — your HTTPS cert is expired, self-signed, or mismatched to the hostname.

## Testing without waiting for real events

Fire a synthetic delivery via `POST /v1/webhooks/{id}/test`:

```bash theme={}
curl https://api.antonpayments.dev/v1/webhooks/$WBH_ID/test \
  -X POST \
  -H "Authorization: DPoP $ANTON_ACCESS_TOKEN"
```

Anton sends a `test` event to your subscription. Use it to validate connectivity, signature verification, and response handling before depending on real events in sandbox tests.

## Secret rotation

When a secret might be compromised — or on a schedule — rotate it:

```bash theme={}
curl https://api.antonpayments.dev/v1/webhooks/$WBH_ID/secret/rotate \
  -X POST \
  -H "Authorization: DPoP $ANTON_ACCESS_TOKEN"
```

The response contains the new secret — again, shown once. **Rotation has no overlap window** — the old secret is invalidated immediately. Plan rotations for low-traffic periods, and update your receiver's secret in lockstep.

## Deactivation vs deletion

* **Deactivate** (`POST /v1/webhooks/{id}/deactivate`) — stop delivering events. Subscription stays as a record; reactivation may be a supported future path.
* **Delete** (`DELETE /v1/webhooks/{id}`) — permanent. Event history remains for audit, but no new deliveries.

Prefer deactivate during incidents so you can resume later.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook event catalog" icon="bolt" href="/api-reference/webhook-events">
    Every event type and its payload schema.
  </Card>

  <Card title="Webhook signing" icon="signature" href="/api-reference/webhook-signing">
    Copy-paste HMAC verification in 4 languages.
  </Card>
</CardGroup>
