> For the complete documentation index, see [llms.txt](https://docs.xpayconnect.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xpayconnect.io/documentation/eng/concepts/webhooks.md).

# Webhooks

After the order status changes to **success**, the system sends a POST request to the URL specified in the `success_callback_url` field when creating the order.

{% hint style="info" %}
If `success_callback_url` is not specified when creating the order, no webhook will be sent.
{% endhint %}

## Webhook Format

Example for a fiat method with conversion to USDT (`convertToUsdt: true`):

```json
{
    "id": "lux01993328-a828-7581-b3a9-e712a6a0e88c",
    "order_id": "uE4wBDWPEN77F9FzXA1w8NbVSB",
    "type": "card",
    "amount": "2300",
    "currency": "RUB",
    "status": "success",
    "created_at": "2025-02-06 13:00:13.276",
    "amountAfterFee": 2001,
    "usdtAmount": 25.41,
    "usdtAmountAfterFee": 22.11,
    "exchangeRate": 90.5
}
```

| Field                  | Type              | Description                                                                                                                                                                                                                   |
| ---------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **id**                 | string            | Unique order identifier (internal\_id)                                                                                                                                                                                        |
| **order\_id**          | string            | Merchant-side identifier (external\_id)                                                                                                                                                                                       |
| **type**               | string (enum)     | [Payment method](/documentation/eng/orders/create.md#platezhnye-metody)                                                                                                                                                       |
| **amount**             | string            | Payment amount in `currency` currency (gross, before merchant fee deduction). Integer as a string                                                                                                                             |
| **currency**           | string (enum)     | Order currency: `RUB`, `KGS`, `KZT`, `UZS`. Matches the currency specified when creating the order                                                                                                                            |
| **status**             | string            | Always `success` (webhooks are sent only upon successful order processing)                                                                                                                                                    |
| **created\_at**        | string (datetime) | Order creation date and time                                                                                                                                                                                                  |
| **amountAfterFee**     | number, optional  | Amount in fiat currency after merchant fee deduction — the exact value credited to the merchant balance on success                                                                                                            |
| **usdtAmount**         | number, optional  | USDT amount **WITHOUT** fee deduction. For fiat methods = `amount / exchangeRate` (only with `convertToUsdt: true`). For `usdt_trc20` = amount from `cryptoAmount` (what the client paid). Absent if neither condition is met |
| **usdtAmountAfterFee** | number, optional  | Exact USDT amount credited to the merchant's USDT balance after fee deduction. Populated with `convertToUsdt: true` (fiat methods)                                                                                            |
| **exchangeRate**       | number, optional  | Fixed USDT/`currency` rate (populated with `convertToUsdt: true` for fiat methods; absent for `usdt_trc20`)                                                                                                                   |
| **cryptoAmount**       | string, optional  | Exact crypto amount actually paid by the client. Returned for crypto methods (`usdt_trc20`, etc.). Absent for fiat methods                                                                                                    |

**Examples for crypto methods:**

{% tabs %}
{% tab title="usdt\_trc20" %}
Merchant on fiat balance without `convertToUsdt`:

```json
{
    "id": "lux01993328-...",
    "order_id": "uE4wBDWPEN77F9FzXA1w8NbVSB",
    "type": "usdt_trc20",
    "amount": "5000",
    "currency": "RUB",
    "status": "success",
    "created_at": "2026-05-01 10:15:42.118",
    "amountAfterFee": 3900,
    "usdtAmount": 50,
    "cryptoAmount": "50.00000000"
}
```

`usdtAmount` is taken from `cryptoAmount` (the amount is already in USDT). `exchangeRate` is absent. `usdtAmountAfterFee` is absent if the merchant has a fiat balance — they receive fiat `amountAfterFee` minus the fee.
{% endtab %}

{% tab title="ton" %}

```json
{
    "id": "lux01993328-...",
    "order_id": "uE4wBDWPEN77F9FzXA1w8NbVSB",
    "type": "ton",
    "amount": "5000",
    "currency": "RUB",
    "status": "success",
    "created_at": "2026-05-01 10:15:42.118",
    "amountAfterFee": 4750,
    "usdtAmount": 50,
    "cryptoAmount": "50.00000000"
}
```

`usdtAmount` equals `cryptoAmount` — both contain the amount in TON. `exchangeRate` is absent. `convertToUsdt: true` is not supported for `ton`.
{% endtab %}
{% endtabs %}

***

## Webhook Signature

The webhook contains an **x-api-key** header with a SHA-256 hash. To verify, generate a signature the same way as [request authorization](/documentation/eng/concepts/auth.md) and compare it with the received value.

{% tabs %}
{% tab title="JavaScript" %}

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

function verifyWebhook(body, receivedHash, apiKey) {
    const bodyStr = JSON.stringify(body);
    const expected = crypto.createHash('sha256').update(`${apiKey}|${bodyStr}`).digest('hex');
    return expected === receivedHash;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import json

def verify_webhook(body: dict, received_hash: str, api_key: str) -> bool:
    body_str = json.dumps(body, separators=(',', ':'))
    expected = hashlib.sha256(f'{api_key}|{body_str}'.encode()).hexdigest()
    return expected == received_hash
```

{% endtab %}

{% tab title="PHP" %}

```php
function verifyWebhook(array $body, string $receivedHash, string $apiKey): bool {
    $bodyStr = json_encode($body, JSON_UNESCAPED_UNICODE);
    $expected = hash('sha256', $apiKey . '|' . $bodyStr);
    return hash_equals($expected, $receivedHash);
}
```

{% endtab %}
{% endtabs %}

***

## Webhook Response

In response to a webhook, the merchant server must return HTTP **200**. For any other response code, the system will retry delivery:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | 15 seconds |
| 2       | 1 minute   |
| 3       | 5 minutes  |
| 4       | 15 minutes |
| 5       | 1 hour     |

{% hint style="warning" %}
After 5 failed attempts, delivery stops. Use the [Order Information](/documentation/eng/orders/info.md) endpoint to check the status manually.
{% endhint %}
