> 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/payments-api/webhook.md).

# Webhook

When a payment (deposit) is detected and progresses on-chain, PayRam POSTs a webhook to your registered URL so you can track it without polling. Your server must accept the request, parse the body, and respond with a <mark style="color:$warning;">`2xx`</mark> status to acknowledge receipt.

> This is the payment / deposit webhook (incoming funds against a payment session). For outgoing payouts, see the Payout Webhooks section of the Payouts API doc.

### How to set up a Webhook?

You register webhook endpoints from the PayRam dashboard:

1. Open the PayRam dashboard.
2. Go to **Settings → Webhook**.
3. Click **Add Endpoint**.
4. Enter your **Endpoint URL** (a publicly reachable **HTTPS** URL) and a short description, then save.

Mark the endpoint **active**. PayRam delivers events to every active endpoint registered for the project.

> 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 and Retries

* Method: <mark style="color:$warning;">**`POST`**</mark>, <mark style="color:$warning;">`Content-Type: application/json`</mark>. (It is a POST with a JSON body — not a GET.)
* **Verify authenticity** of every delivery using either header:
  * <mark style="color:$warning;">**`X-Payram-Signature`**</mark> (recommended) — HMAC-SHA256 of the **raw request body**, keyed with your project API key, formatted <mark style="color:$warning;">`sha256=<hex>`</mark>. Recompute and constant-time compare.
  * <mark style="color:$warning;">**`API-KEY`**</mark> — your project API key sent verbatim (legacy; kept for backward compatibility).
* **Confirmation-progress** deliveries (while a deposit is still confirming) are retried up to 3 times per cycle (immediately, then after 2s and 4s) and re-sent on the next poll until the payment is filled.
* **Final** deliveries (payment <mark style="color:$warning;">`closed`</mark> / <mark style="color:$warning;">`cancelled`</mark>) are retried quickly (0s, 2s, 4s), then scheduled for **long-term retry** (30m, 1h, 2h, …) 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 and triggers a retry.
* Treat events as **idempotent** — you may receive the same status more than once. Key off <mark style="color:$warning;">`reference_id`</mark> (or <mark style="color:$warning;">`invoice_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 <mark style="color:$warning;">`X-Payram-Signature`</mark> header:

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

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

### Payment Status

The status field reflects how much of the payment has been filled:

| <mark style="color:$warning;">`status`</mark>           | Meaning                                                                         |
| ------------------------------------------------------- | ------------------------------------------------------------------------------- |
| <mark style="color:$warning;">`OPEN`</mark>             | Payment created; no deposit detected yet (or filled amount is zero).            |
| <mark style="color:$warning;">`PARTIALLY_FILLED`</mark> | A deposit was detected but the filled amount is less than the requested amount. |
| <mark style="color:$warning;">`FILLED`</mark>           | The filled amount equals the requested amount.                                  |
| <mark style="color:$warning;">`OVER_FILLED`</mark>      | The filled amount exceeds the requested amount.                                 |
| <mark style="color:$warning;">`CANCELLED`</mark>        | The payment was cancelled.                                                      |

### Confirmation Progress

While a deposit is confirming on-chain, PayRam sends progress webhooks carrying <mark style="color:$warning;">`confirmation_current`</mark> / <mark style="color:$warning;">`confirmation_required`</mark> (e.g. <mark style="color:$warning;">`3 / 12`</mark>, <mark style="color:$warning;">`5 / 12`</mark>) so you can show progress until the payment is filled. On the final closed/cancelled webhook these are <mark style="color:$warning;">`0`</mark>.

The typical flow:

```
OPEN  →  (deposit detected → confirming: 1/N, 2/N … N/N)  →  FILLED   (or PARTIALLY_FILLED / OVER_FILLED)
OPEN  →  CANCELLED
```

### Payload

All monetary amounts are JSON strings; <mark style="color:$warning;">`timestamp`</mark>, <mark style="color:$warning;">`confirmation_*`</mark>, and <mark style="color:$warning;">`block_number`</mark> are numbers. <mark style="color:$warning;">`filled_amount`</mark> / <mark style="color:$warning;">`filled_amount_in_usd`</mark> may be <mark style="color:$warning;">`null`</mark> before a deposit is detected, and `payment_info` is empty until there’s an on-chain deposit.

```json
{
  "customer_id": "1234",
  "invoice_id": "INV-0090",
  "reference_id": "a1b2c3d4e5",
  "status": "FILLED",
  "amount": "323.53",
  "currency": "USDT",
  "filled_amount": "323.53",
  "filled_amount_in_usd": "323.53",
  "sponsored_amount": "0",
  "sponsored_amount_in_usd": "0",
  "timestamp": 1750340282,
  "payment_info": [
    {
      "source_address": "0x7a2C…9bF1",
      "transaction_hash": "0xabc123…def456",
      "destination_address": "0x21d4cF2E…EA8d45",
      "block_number": 21897412
    }
  ],
  "confirmation_current": 12,
  "confirmation_required": 12
}
```

| Field                                                                     | Type           | Meaning                                                                                                                                                   |
| ------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:$warning;">`customer_id`</mark>                        | string         | Your identifier for the paying customer.                                                                                                                  |
| <mark style="color:$warning;">`invoice_id`</mark>                         | string         | The invoice this payment belongs to.                                                                                                                      |
| <mark style="color:$warning;">`reference_id`</mark>                       | string         | PayRam’s unique reference for the payment (use as the idempotency key).                                                                                   |
| <mark style="color:$warning;">`status`</mark>                             | string         | Fill state — see the table above.                                                                                                                         |
| <mark style="color:$warning;">`amount`</mark>                             | string         | Requested payment amount in <mark style="color:$warning;">`currency`</mark>.                                                                              |
| <mark style="color:$warning;">`currency`</mark>                           | string         | Currency code (e.g. <mark style="color:$warning;">`BTC`</mark>, <mark style="color:$warning;">`USDT`</mark>, <mark style="color:$warning;">`ETH`</mark>). |
| <mark style="color:$warning;">`filled_amount`</mark>                      | string \| null | Amount received so far (null before any deposit).                                                                                                         |
| <mark style="color:$warning;">`filled_amount_in_usd`</mark>               | string \| null | USD value of the filled amount.                                                                                                                           |
| <mark style="color:$warning;">`sponsored_amount`</mark>                   | string         | Gas/fee amount sponsored by PayRam (<mark style="color:$warning;">`"0"`</mark> if none).                                                                  |
| <mark style="color:$warning;">`sponsored_amount_in_usd`</mark>            | string         | USD value of the sponsored amount.                                                                                                                        |
| <mark style="color:$warning;">`timestamp`</mark>                          | number         | Last-update time, Unix epoch **seconds**.                                                                                                                 |
| <mark style="color:$warning;">`payment_info`</mark>                       | array          | On-chain deposit details (empty until a deposit is detected).                                                                                             |
| <mark style="color:$warning;">`payment_info[].source_address`</mark>      | string         | Address the funds came from.                                                                                                                              |
| <mark style="color:$warning;">`payment_info[].transaction_hash`</mark>    | string         | On-chain transaction hash of the deposit.                                                                                                                 |
| <mark style="color:$warning;">`payment_info[].destination_address`</mark> | string         | PayRam deposit address that received the funds.                                                                                                           |
| <mark style="color:$warning;">`payment_info[].block_number`</mark>        | number         | Block the deposit was included in.                                                                                                                        |
| <mark style="color:$warning;">`confirmation_current`</mark>               | number         | Confirmations seen so far (<mark style="color:$warning;">`0`</mark> on the final webhook).                                                                |
| <mark style="color:$warning;">`confirmation_required`</mark>              | number         | Confirmations required before the payment is considered settled.                                                                                          |

### Acknowledging

Return <mark style="color:$warning;">`2xx`</mark> to acknowledge. If your endpoint is unreachable, errors, or times out, PayRam retries (quick retries, then long-term backoff for final events). Respond quickly and process asynchronously.
