# Operate: Webhooks

Webhooks are how you learn about a payment you did not initiate: an alternative payment method that settles minutes later, a refund the provider finalises, a dispute. Polling with `force_sync` is the fallback, not the mechanism.

## What this page covers
A configure-and-observe flow:

1. **Read the current configuration** — `GET /account/{account_id}/business_profile/{profile_id}`. Webhook settings live on the profile, not the payment.
2. **Point the webhook at your endpoint** — `POST` the same path with `webhook_details`. The page prefills this session's listener URL.
3. **Cause a delivery** — create a payment that confirms immediately, so there is something to deliver.
4. **Watch the delivery land** — the live event feed for this session.
5. **Delivery attempts and retries** — documented, not callable (see below).

## Configuring deliveries

```json
"webhook_details": {
  "webhook_url": "https://your-domain.com/webhooks/peach",
  "webhook_username": null,
  "webhook_password": null,
  "payment_created_enabled": true,
  "payment_succeeded_enabled": true,
  "payment_failed_enabled": true,
  "payment_statuses_enabled": ["succeeded", "failed", "partially_captured", "requires_merchant_action"],
  "refund_statuses_enabled": ["succeeded", "failed"]
},
"outgoing_webhook_custom_http_headers": { "X-Custom-Header": "your-value" }
```

| Field | Meaning |
|---|---|
| `payment_created_enabled` | A payment intent was created |
| `payment_succeeded_enabled` | Money captured |
| `payment_failed_enabled` | Declined or abandoned |
| `payment_statuses_enabled` | An explicit list of statuses, for finer control than the three booleans |
| `refund_statuses_enabled` | Refunds reaching succeeded or failed |
| `payout_statuses_enabled` | Payout state changes |

`outgoing_webhook_custom_http_headers` adds headers to every delivery, which is the usual way to authenticate the call to your own endpoint.

## Delivery attempts and retries need dashboard auth

These three exist on the API but return `IR_04` (missing Authorization) or `IR_01` (invalid API key) when called with a merchant API key — they expect a dashboard session:

```
POST /events/profile/list                      # list events for the profile
GET  /events/{merchant_id}/{event_id}/attempts # every attempt, with response codes
POST /events/{merchant_id}/{event_id}/retry    # replay after fixing your endpoint
```

Inspect and replay deliveries from the Peach Dashboard, or with an admin key if you have one.

## Key concepts
- **Design your receiver for replay.** Deliveries are retried, so your endpoint will see the same event more than once. Key on `payment_id` plus status and make applying it idempotent.
- **Return 2xx quickly** and do the work asynchronously. A slow endpoint looks like a failed one and gets retried.
- **Events can arrive out of order.** Treat an event as a nudge to reconcile — fetch the payment and act on what it says — rather than as the truth.
- Peach's own webhooks to Hyperswitch are AES-GCM encrypted and applied automatically; the deliveries described here are the ones Hyperswitch then sends to you.
- **Reachability matters.** A localhost listener will never receive anything even though the configuration saves successfully. Use a tunnel or a deployed instance.

## Related
- [Operate: profile configuration](/operate/profile.md): the rest of the profile these settings live on
- [Operate: transactions](/operate/transactions.md): reconcile against what the payment actually says
- [Payment states](/payment-states.md): the statuses events are reporting

---

Interactive version: https://playground.peachpayments.com/operate/webhooks
