> For the complete documentation index, see [llms.txt](https://docs.payram.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.payram.com/api-integration/payouts-apis/payout-webhooks.md).

# Payout Webhooks

<figure><img src="/files/zwm9C3WmMen7bhNyxDfb" alt=""><figcaption></figcaption></figure>

Instead of polling, let PayRam push status changes to you. Whenever a payout transitions, PayRam **POSTs** a JSON event to your registered webhook URL — so you learn about `sent`, `processed`, `failed`, etc. as they happen.

> **Requires PayRam v3.1.1 or later.**

### Setup

1. Register your webhook URL in the dashboard for the project (**Project → Webhooks**) and set it to **active**.
2. The endpoint must be a publicly reachable **HTTPS** URL that responds with a <mark style="color:$warning;">`2xx`</mark> status.

Webhooks are **on by default**. (They can be disabled server-side with the <mark style="color:$warning;">`SEND_WEBHOOK_TO_MERCHANT=false`</mark> environment variable.)

### Delivery & retries

* Method: <mark style="color:$warning;">**`POST`**</mark>, <mark style="color:$warning;">`Content-Type: application/json`</mark>.
* **Verify authenticity** of every delivery using either header:
  * <mark style="color:$warning;">**`X-Payram-Signature`**</mark> (recommended) — an HMAC-SHA256 of the **raw request body**, keyed with your project API key, formatted <mark style="color:$warning;">`sha256=<hex>`</mark>. Recompute it on your side and constant-time compare.
  * <mark style="color:$warning;">**`API-KEY`**</mark> — your project API key sent verbatim (legacy; kept for backward compatibility).
* Each delivery is retried up to **3 times** (immediately, then after 2s and 4s) until your endpoint returns a <mark style="color:$warning;">`2xx`</mark>. Any response <mark style="color:$warning;">`≥ 400`</mark> (or a timeout — the client waits up to 60s) counts as a failure.
* The payout’s <mark style="color:$warning;">`webhookStatus`</mark> becomes <mark style="color:$warning;">`received`</mark> once your endpoint accepts a delivery, or <mark style="color:$warning;">`failed`</mark> if all attempts fail.
* Return <mark style="color:$warning;">`2xx`</mark> quickly and process asynchronously; treat events as **idempotent** (you may receive the same status more than once) and key off <mark style="color:$warning;">`payout_id`</mark> + <mark style="color:$warning;">`status`</mark>.

#### Verifying the signature

Compute the HMAC over the **exact raw bytes** of the request body (do not re-serialize the parsed JSON) and compare against the `X-Payram-Signature` header:

```jsx
const crypto = require('crypto');

function verifyPayramSignature(rawBody, signatureHeader, apiKey) {
  const expected =
    'sha256=' + crypto.createHmac('sha256', apiKey).update(rawBody).digest('hex');
  // constant-time compare
  return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
```

### Event types

The <mark style="color:$warning;">`event_type`</mark> is <mark style="color:$warning;">`payout.<status>`</mark> (lowercase). Note the status is the **webhook status label**, which maps from the payout’s lifecycle state:

| Payout <mark style="color:$warning;">`status`</mark> (API)                         | Webhook <mark style="color:$warning;">`status`</mark>   | <mark style="color:$warning;">`event_type`</mark>              |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------- |
| <mark style="color:$warning;">`pending-approval / pending-otp-verification`</mark> | <mark style="color:$warning;">`PENDING-APPROVAL`</mark> | <mark style="color:$warning;">`payout.pending-approval`</mark> |
| <mark style="color:$warning;">`pending`</mark>                                     | <mark style="color:$warning;">`APPROVED`</mark>         | <mark style="color:$warning;">`payout.approved`</mark>         |
| <mark style="color:$warning;">`initiated`</mark>                                   | <mark style="color:$warning;">`INITIATED`</mark>        | <mark style="color:$warning;">`payout.initiated`</mark>        |
| <mark style="color:$warning;">`sent`</mark>                                        | <mark style="color:$warning;">`SENT`</mark>             | <mark style="color:$warning;">`payout.sent`</mark>             |
| <mark style="color:$warning;">`processed`</mark>                                   | <mark style="color:$warning;">`PROCESSED`</mark>        | <mark style="color:$warning;">`payout.processed`</mark>        |
| <mark style="color:$warning;">`failed`</mark>                                      | <mark style="color:$warning;">`FAILED`</mark>           | <mark style="color:$warning;">`payout.failed`</mark>           |
| <mark style="color:$warning;">`rejected`</mark>                                    | <mark style="color:$warning;">`REJECTED`</mark>         | <mark style="color:$warning;">`payout.rejected`</mark>         |
| <mark style="color:$warning;">`cancelled`</mark>                                   | <mark style="color:$warning;">`CANCELLED`</mark>        | <mark style="color:$warning;">`payout.cancelled`</mark>        |

### Payload

```json
{
  "event_type": "payout.sent",
  "payout_id": 120,
  "network": "ETH",
  "token": "USDC",
  "amount": "100",
  "amount_usd": "100.000000",
  "email": "test@test.com",
  "address": "0x9F8E7D6C5B4A39281706F5E4D3C2B1A098765432",
  "from_address": "0x21d4cF2E…EA8d45",
  "tx_hash": "0xabc123…def456",
  "status": "SENT",
  "withdrawal_type": "payout_merchant",
  "currency_type": "token",
  "created_at": 1750340081,
  "updated_at": 1750340282,
  "timestamp": 1750340282,
  "failure_reason": ""
}
```

| Field                                                                      | Meaning                                                                                                                                |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:$warning;">`event_type`</mark>                          | <mark style="color:$warning;">`payout.<status>`</mark> — the event that fired.                                                         |
| <mark style="color:$warning;">`payout_id`</mark>                           | The payout <mark style="color:$warning;">`id`</mark> (matches the <mark style="color:$warning;">`id`</mark> from Create / Get Payout). |
| <mark style="color:$warning;">`network / token`</mark>                     | Blockchain code and currency.                                                                                                          |
| <mark style="color:$warning;">`amount / amount_usd`</mark>                 | Crypto amount sent and its USD value.                                                                                                  |
| <mark style="color:$warning;">`email / address`</mark>                     | Recipient email and destination wallet address.                                                                                        |
| <mark style="color:$warning;">`from_address`</mark>                        | Project hot wallet that paid out (empty until assigned).                                                                               |
| <mark style="color:$warning;">`tx_hash`</mark>                             | On-chain hash (empty until <mark style="color:$warning;">`initiated`</mark>).                                                          |
| <mark style="color:$warning;">`status`</mark>                              | Uppercase webhook status (see the mapping table).                                                                                      |
| <mark style="color:$warning;">`withdrawal_type`</mark>                     | <mark style="color:$warning;">`payout_merchant`</mark> for payouts created via this API.                                               |
| <mark style="color:$warning;">`currency_type`</mark>                       | <mark style="color:$warning;">`token`</mark> (ERC20/TRC20) or <mark style="color:$warning;">`coin`</mark> (native ETH/POL/TRX).        |
| <mark style="color:$warning;">`created_at / updated_at / timestamp`</mark> | Unix epoch seconds.                                                                                                                    |
| <mark style="color:$warning;">`failure_reason`</mark>                      | Reason string on <mark style="color:$warning;">`payout.failed`</mark> (empty otherwise).                                               |

> Always respond <mark style="color:$warning;">`2xx`</mark> to acknowledge. If your endpoint is unreachable or errors, PayRam marks the delivery <mark style="color:$warning;">`failed`</mark> after retries — you can still reconcile via **Get All Payouts** / **Payout Status**.
