# Webhooks

## Basics

Merchant systems can receive real-time notifications from the xMoney platform via webhooks. Currently, the available events are:

- **Order payment detected** (an incoming payment has been seen on-chain but is not yet confirmed)
- **Order payment confirmation**
- **Order cancellation**


The webhook URL is specified using the `callback_url` field when creating an order.

### Response Handling & Retries

Webhook requests must respond with an **HTTP 2xx status** (specifically `200`-`207`) to indicate success. Any other response or a lack of response is considered a failure, triggering a retry mechanism.

After the initial delivery attempt, xMoney retries up to **15** more times (for a maximum of **16 delivery attempts**). Attempts are made at fixed minute offsets that follow a Fibonacci sequence, measured from the moment the event is first queued:

```
0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 (minutes)
```

The last attempt happens **987 minutes (~16 hours 27 minutes)** after the event is queued. If every attempt fails, the webhook is marked as failed and an email notification with error details is sent.

### Security & Authentication

All webhook requests from xMoney are **signed** using a Webhooks Secret for validation.

- Webhooks are automatically set up and validated in our integrations.
- For custom integrations, we provide [**libraries**](/guides/crypto/integrations#libraries) for request validation.
- Each webhook request is **signed** to prevent unauthorized access.


## Events

Events notify merchants of order-related activities in real time. Webhooks deliver these events to the specified `callback_url`.

### Supported Events

| Event Name | `state` | Description |
|  --- | --- | --- |
| `ORDER.PAYMENT.DETECTED` | `detected` | An incoming payment has been detected on-chain but is not yet confirmed. |
| `ORDER.PAYMENT.RECEIVED` | `completed` | Payment successfully confirmed. |
| `ORDER.PAYMENT.CANCELLED` | `cancelled` | Payment was canceled (for example, the order expired). |


### Payload fields

Every webhook request has the following top-level fields:

| Field | Description |
|  --- | --- |
| `event_type` | The event name (see the table above). |
| `state` | The internal state corresponding to the event (`detected`, `completed`, or `cancelled`). |
| `resource` | An object describing the order/payment (see below). |
| `signature` | The HMAC-SHA256 signature of the payload (see [Verifying Events](#verifying-events)). |
| `encrypted_signature` | Optional. An additional signature included only for specific whitelisted integrations (for example, xPortal and onramp partners). Standard integrations can ignore it. |


The `resource` object always includes the following fields:

| Field | Description |
|  --- | --- |
| `reference` | Your internal reference for the order. |
| `amount` | The fiat amount of the order, formatted as a decimal string. |
| `currency` | The 3-letter fiat currency code (ISO 4217). |


For `ORDER.PAYMENT.RECEIVED` events, some whitelisted integrations additionally receive crypto payment details in `resource`: `tx_hash`, `crypto_currency`, `blockchain_network`, `crypto_amount`, and, where applicable, `refund_crypto_amount` and `refundable`. These extra fields are not sent for standard integrations.

## Verifying Events

To ensure authenticity, webhook events are signed using **HMAC SHA256** with the Webhooks Secret as the key. The payload is **sorted alphabetically** before signing.

### Example Payload

```json
{
  "event_type": "ORDER.PAYMENT.RECEIVED",
  "resource": {
    "reference": "1400012634",
    "amount": "10.8200",
    "currency": "EUR"
  },
  "signature": "5ef8a5994e917c14479b31f690d4d2a023dfcc6059081504e3087977b21580ab",
  "state": "completed"
}
```

### Ordered Payload for Signing

```json
{
  "event_type": "ORDER.PAYMENT.RECEIVED",
  "resource": {
    "amount": "10.8200",
    "currency": "EUR",
    "reference": "1400012634"
  },
  "state": "completed"
}
```

### Signing Process

```bash
joined_payload="event_typeORDER.PAYMENT.RECEIVEDresourceamount10.8200resourcecurrencyEURresourcereference1400012634statecompleted"
signed_payload=$(echo -n "$joined_payload" | openssl dgst -sha256 -hmac "$secret")
```

The resulting signature should match the one in the webhook request.

For a PHP validation example, see [this reference](https://github.com/utrustdev/utrust-php/blob/e400f219b73cced5b184f2f15eded3c8654dce3c/src/Webhook/Event.php#L75).

## Response & Retries

Webhook responses must follow these rules:

- **Success (200 OK)** → `{ "success": true }` (optional JSON response)
- **Invalid signature or malformed request (400 Bad Request)**
- **Internal processing errors (500 Internal Server Error)**


If the webhook fails, retries occur as per the Fibonacci backoff schedule described above (the initial attempt plus up to 15 retries, for 16 delivery attempts in total). After that, an email notification is sent with failure details.