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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.payram.com/api-integration/payments-api/webhook.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
