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

# Webhooks

> Webhooks keep your system in sync with what’s happening inside Acclaim. Instead of polling the API, you can receive real-time notifications whenever key events occur — like a payout completing or failing.

Payments are event-driven. Once you create a payout, it may take seconds or days to complete depending on the payment method and banking networks involved. Webhooks let you:

* Update internal records when payouts succeed or fail.
* Trigger notifications to your team or payees.
* Automate reconciliation and reporting.

## Setting Up a Webhook Endpoint

A webhook endpoint is just an HTTPS URL on your server that can accept `POST` requests.

When something happens — like a payout completing — Acclaim sends a JSON payload to your endpoint.

Key considerations when building your endpoint:

* Must accept `POST` requests with a JSON body.
* Must return a `2xx` HTTP status code to acknowledge receipt.
* Should be idempotent (able to safely handle duplicate deliveries).

Example flow:

1. Acclaim sends an event payload to your webhook URL.
2. Your server processes the event.
3. Your server responds with `200 OK` to confirm receipt.

### Installing Webhooks in the Console

You can install and manage webhook endpoints directly from the **Acclaim Console** under **Settings → Developers → Webhooks**.

* Add one or more HTTPS URLs where Acclaim should send events.
* Choose which event types to subscribe to, or receive all events by default.
* Test delivery right from the console to verify your endpoint is working.

## Event Payload Structure

All webhook events share a consistent format:

<CodeGroup>
  ```json JSON theme={null}
  {
    "id": "evt_MjdYqzLbsS",
    "type": "payout.succeeded",
    "account_id": "acct_ovGlkewETl",
    "created_at": "2025-10-08T18:20:31Z",
    "data": {
        "id": "po_vkj7BPRPr9",
        "payee_id": "pyee_PXlpcv13X9",
        "payment_amount": 50000,
        "payment_currency": "usd"
        // truncated for brevity
    }
  }
  ```
</CodeGroup>

<ResponseField name={"id"} type="string">
  Unique event identifier.
</ResponseField>

<ResponseField name={"type"} type="string">
  Event type, such as `payout.completed`.
</ResponseField>

<ResponseField name={"account_id"} type="string">
  The account ID that generated the event.
</ResponseField>

<ResponseField name={"created_at"} type="datetime">
  Timestamp of the event.
</ResponseField>

<ResponseField name={"data"} type="object">
  The resource that changed (e.g., payout details). This will match the same format as retrieving the resource through the API.
</ResponseField>

## Event Types

### Payer Events

* `payer.created`
* `payer.updated`
* `payer.deleted`

### Payment Request Events

* `payment_request.created`
* `payment_request.updated`
* `payment_request.succeeded`
* `payment_request.failed` — see [Payment failures](/guides/collect/payment-failures#failure-codes) for failure code reference
* `payment_request.canceled`

### Setup Request Events

* `setup_request.created`
* `setup_request.updated`
* `setup_request.succeeded`
* `setup_request.failed`
* `setup_request.canceled`

### Refund Events

* `refund.created`
* `refund.failed`
* `refund.succeeded`

### Dispute Events

* `dispute.created`

### Payee Events

* `payee.created`
* `payee.updated`
* `payee.deleted`

### Payout Events

* `payout.created`
* `payout.updated`
* `payout.processing`
* `payout.succeeded`
* `payout.failed`

### Payout Batch Events

* `payout_batch.created`
* `payout_batch.updated`
* `payout_batch.succeeded`
* `payout_batch.failed`
* `payout_batch.canceled`

### Treasury Account Events

* `treasury.account.created`
* `treasury.account.updated`

### Treasury Transaction Events

* `treasury.transaction.created`

## Reliability & Retries

Acclaim automatically retries failed webhook deliveries for up to **48 hours** using exponential backoff.

Your endpoint should:

* Be idempotent: safely handle duplicate events.
* Respond quickly: return `2xx` as soon as the event is accepted, then process asynchronously if needed.
* Log event `id`s to avoid reprocessing the same event.

If all retry attempts fail, the event will be marked as undelivered in the Acclaim Console.

## Securing Webhooks

Webhooks should be secure so only Acclaim can call them:

* Use HTTPS for encryption.
* Verify the webhook signature before you process the event.

### Signature verification

Each webhook request includes these headers:

* `Acclaim-Timestamp` - The Unix timestamp used when the request was signed.
* `Acclaim-Signature` - The HMAC signature for the request body, in the format `v1=<hex_digest>`.

Acclaim signs the exact request body bytes using HMAC SHA-256 and your webhook endpoint secret.

To verify a webhook:

1. Read the raw request body exactly as it was received.
2. Read the `Acclaim-Timestamp` header.
3. Build the signed payload as `timestamp.raw_body`, with a literal `.` between the timestamp and raw body.
4. Compute an HMAC SHA-256 digest using your webhook endpoint secret.
5. Prefix the digest with `v1=` and compare it to the `Acclaim-Signature` header using a constant-time comparison.
6. Reject the request if the signature does not match.

<Tip>
  Use the raw request body for verification. If your framework parses and re-serializes the JSON before verification, the signature check can fail.
</Tip>

Example verification flow in JavaScript:

```javascript theme={null}
import crypto from 'node:crypto';

const timestamp = req.header('Acclaim-Timestamp') ?? '';
const signatureHeader = req.header('Acclaim-Signature') ?? '';
const rawBody = req.rawBody;

const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
  .createHmac('sha256', webhookSecret)
  .update(signedPayload, 'utf8')
  .digest('hex');
const expectedHeader = `v1=${expectedSignature}`;

if (!crypto.timingSafeEqual(Buffer.from(expectedHeader), Buffer.from(signatureHeader))) {
  res.status(400).send('Invalid signature');
  return;
}
```

For additional protection, you can also reject requests with old timestamps to reduce replay risk.

## What’s Next

Once your webhook endpoint is live, test it using your sandbox environment. Create a payout and watch events arrive, confirming your system can react to status changes and keep your records up to date.
