# Webhooks

When a payment is completed — whether successful or failed — xMoney can notify you through multiple channels. This ensures that both you and the customer receive immediate feedback, and that your systems stay in sync even if there are browser or network issues.

## Notification URLs

To properly handle payment results, you need to provide:

1. **Server-to-Server Notification URL (Webhook/IPN)**
  * An endpoint on your server to receive Instant Payment Notifications (IPNs).
  * This notification occurs after the payment is processed, crucial if the customer's browser fails or closes before returning to your site.
  * **Configuration:**
    * Access your [Merchant Dashboard](https://dashboard.xmoney.com).
    * Navigate to **Sites** -> **SiteId** -> **Payment Page**.
    * Enter your URL and enable the "Use Server-to-server notification URL" checkbox.
2. **Payment Page Back URL (`backUrl`)**
  * A page on your site to which the customer is redirected immediately after the payment attempt — whether successful or failed.
  * This helps you show the customer a confirmation or error message in real-time.


## Notification flow overview

1. **Browser Redirect:** Immediately after the customer completes or cancels payment, xMoney redirects their browser to your Payment Page Back URL.
2. **Server-to-Server Notification (IPN/Webhook):** xMoney also sends a server-to-server notification to the Notification URL you provided. This message might arrive with a delay if retries are needed (e.g., if your server is temporarily unreachable).


**Important:** Always rely on the Payment Page Back URL for immediate feedback to the customer, and use the IPN/Webhook to update your records in case the browser flow fails or is interrupted.

## Acknowledging the Webhook (IPN)

When you receive the Webhook (IPN), your server must respond with:


```http
HTTP/1.1 200 OK
Content-Type: text/plain

OK
```

To confirm successful processing, your server must respond with `HTTP/1.1 200 OK` and the body content `OK`. Failure to do so will trigger xMoney to retry sending the IPN on a schedule of 1 minute, 5 minutes, 1 hour, and 24 hours, after which retries will cease.

## Payload decryption

Both notification URLs receive payment details via POST. The payload is identical for both URLs and includes the following parameters: `result`, `opensslResult`, and `signature`.

* The `result` parameter is deprecated. Use only `opensslResult`.
* The `opensslResult` parameter is always encrypted. Decrypt it using your **API Key**.


**Decryption Steps:**

1. **String Split:** Divide the encrypted string at the first comma (`,`).
  * Left: Initialization Vector (IV).
  * Right: Encrypted data.
2. **Base64 Decoding:** Decode the IV and encrypted data using Base64.
3. **OpenSSL Decryption:** Use the OpenSSL `aes-256-cbc` algorithm, your API key, and the decoded IV.


#### Example decryption code

NodeJS

```javascript
import crypto from 'crypto';

/**
 * Decrypts an encrypted payload received via POST from xMoney notification URLs.
 *
 * @param {string} encryptedPayload The encrypted payload string.
 * @param {string} secretKey The secret key (your xMoney API key).
 *
 * @returns {object} The decrypted JSON object from the payload.
 */
function decryptNotificationPayload(encryptedPayload, secretKey = 'YOUR API KEY HERE') {
    const encryptedParts = encryptedPayload.split(',', 2);
    const iv = Buffer.from(encryptedParts[0], 'base64');
    const encryptedData = Buffer.from(encryptedParts[1], 'base64');

    const decipher = crypto.createDecipheriv('aes-256-cbc', secretKey, iv);
    let decryptedPayload = Buffer.concat([
        decipher.update(encryptedData),
        decipher.final(),
    ]).toString('utf8');

    return JSON.parse(decryptedPayload);
}
```

PHP

```PHP
<?php

/**
 * Decrypts an encrypted payload received via POST from xMoney notification URLs.
 *
 * @param string $encryptedPayload The encrypted payload string.
 * @param string $secretKey The secret key (your xMoney API key).
 *
 * @return array|null The decrypted JSON object from the payload as an associative array, or null on failure.
 */
function decryptNotificationPayload($encryptedPayload, $secretKey = 'YOUR API KEY HERE') {
    $encryptedPayload = (string) $encryptedPayload;
    if (empty($encryptedPayload)) {
        return null;
    }

    if (strpos($encryptedPayload, ',') !== false) {
        $encryptedParts = explode(',', $encryptedPayload, 2);

        // 1. Decode the IV
        $iv = base64_decode($encryptedParts[0]);
        if ($iv === false) {
            return null;
        }

        // 2. Decode the encrypted data
        $encryptedData = base64_decode($encryptedParts[1]);
        if ($encryptedData === false) {
            return null;
        }

        // 3. Decrypt with OpenSSL
        $decryptedPayload = openssl_decrypt(
            $encryptedData,
            'aes-256-cbc',
            $secretKey,
            OPENSSL_RAW_DATA,
            $iv
        );
        if ($decryptedPayload === false) {
            return null;
        }

        // JSON decode the decrypted data
        return json_decode($decryptedPayload, true);
    }

    return null;
}
```

## Payload data

Both the Payment Page Back URL and the Server-to-Server Notification URL receive the same payload data.

The JSON structure typically includes:


```json
{
  externalOrderId,
  identifier,
  transactionStatus,
  customerId,
  orderId,
  cardId,
  transactionId,
  transactionMethod,
  timestamp,
  amount,
  currency,
  customData, // may be a string or object
}
```

#### Examples

Successful Transaction

```json
{
  "transactionStatus": "complete-ok",
  "orderId": 1234,
  "externalOrderId": "external-order-id",
  "transactionId": 1234,
  "transactionMethod": "card",
  "customerId": 1,
  "identifier": "identifier",
  "amount": 5.55,
  "currency": "EUR",
  "customData": {
    "key_1": "value_1",
    "key_2": "value_2"
  },
  "timestamp": 1600874501,
  "cardId": 3
}
```

Declined Transaction

```json
{
  "transactionStatus": "complete-failed",
  "orderId": 1234,
  "externalOrderId": "external-order-id",
  "transactionId": 1234,
  "transactionMethod": "card",
  "customerId": 1,
  "identifier": "identifier",
  "amount": 0.03,
  "currency": "EUR",
  "customData": {
    "key_1": "value_1",
    "key_2": "value_2"
  },
  "timestamp": 1600871462,
  "cardId": null,
  "errors": [
    {
      "code": 837,
      "message": "Transaction was rejected by the payment provider.",
      "type": "Exception"
    }
  ]
}
```

## Transaction Statuses

The `transactionStatus` key in the response payload indicates the current state of the transaction. Use these statuses to update your order management system, trigger notifications, or display relevant messages.

| Status | Description |
|  --- | --- |
| `start` | The transaction has been initiated but not yet processed. |
| `in-progress` | The transaction is currently being processed. |
| `3d-pending` | The transaction requires customer authentication via 3D Secure. |
| `complete-ok` | The transaction was successfully completed. |
| `complete-failed` | The transaction was declined or encountered an error. |
| `refund-ok` | The transaction was successfully refunded. |
| `void-ok` | The authorized transaction was successfully voided before capture. |


## Conclusion

By setting up both the Payment Page Back URL and the Server-to-Server Notification URL, you ensure reliable, real-time visibility into payment outcomes. Whether the customer's browser returns successfully or not, xMoney's server-to-server notification system will keep your backend updated with the final transaction status.

**Next Steps:**

* Implement and test the decryption logic for `opensslResult` in your environment.
* Confirm that your notification endpoints respond with `200 OK` to avoid repeated IPN attempts.
* Monitor your logs and transaction dashboard to verify successful communication.


With proper webhook and response handling in place, you'll be equipped to provide a seamless, transparent experience for both your customers and your internal order management.