# Webhooks

## Basics

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

- **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** to indicate success. Any other response or a lack of response is considered a failure, triggering a retry mechanism. xMoney retries up to **15 times** using an exponential backoff following a Fibonacci sequence:

1. First retry: **1 minute** after failure
2. Second retry: **2 minutes** after the first retry
3. Third retry: **3 minutes** after the second retry
4. ... up to 987 minutes (~16 hours 27 minutes) after the last failure


If all retries fail, 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 | Description |
|  --- | --- |
| `ORDER.PAYMENT.RECEIVED` | Payment successfully confirmed. |
| `ORDER.PAYMENT.CANCELLED` | Payment was canceled. |


## 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, up to 15 times. After that, an email notification is sent with failure details.