# Webhook

#### Overview

Tylt provides a webhook mechanism for merchants to receive real-time updates on the status of their transactions, whether for pay-ins. Merchants can specify a `callBackUrl` in their API requests, and Tylt will send notifications to this URL whenever there is a status change in the transaction.

#### Setting Up the Webhook

1. **Implement a Callback Endpoint:** Merchants must set up an HTTP POST endpoint that can receive JSON payloads. This endpoint should be capable of processing the incoming webhook data and verifying its authenticity using HMAC-SHA256 signature validation.
2. **Insert the Callback URL:** During a pay-in request, insert your endpoint URL in the `callBackUrl` field. Tylt will send updates to this URL whenever the transaction status changes.
3. **Status Updates:** When a transaction status changes to `Pending`, `Completed`, `Under Payment`, `Over Payment`, or `Expired` Tylt will send a JSON payload with the updated status.
4. **Callback Validation:** To ensure the integrity and authenticity of the callback, Tylt signs each callback payload using HMAC-SHA256 with the merchant’s API secret key. This signature is sent in the HTTP header `X-TLP-SIGNATURE`.
5. **Acknowledge the Callback:** Upon receiving the callback, merchants must respond with an HTTP 200 status code and the text `"ok"` in the response body. This acknowledges the successful receipt of the callback. If the acknowledgment is not received, the webhook will not be retried automatically. Merchants can manually resend webhooks from their Tylt dashboard.

#### Validating Callbacks

Merchants should validate the HMAC signature included in the `X-TLP-SIGNATURE` header to ensure the callback is from Tylt and has not been tampered with. The HMAC signature is generated using the raw POST data and the `MERCHANT_API_SECRET` as the shared key.

#### Example Web-hook Handling Code

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

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

const app = express();
const PORT = 3000;
const apiSecretKey = 'YOUR_TLP_API_SECRET_KEY'; // Replace with your actual API secret key

// Middleware to parse incoming JSON requests
app.use(express.json());

// Callback endpoint
app.post('/callback', (req, res) => {
    const data = req.body;

    // Calculate HMAC signature
    const tlpSignature = req.headers['x-tlp-signature'];
    const calculatedHmac = crypto
        .createHmac('sha256', apiSecretKey)
        .update(JSON.stringify(data)) // Use raw body string for HMAC calculation
        .digest('hex');

    if (calculatedHmac === tlpSignature) {
        // Signature is valid
        if (data.type === 'pay-in') {
            console.log('Received pay-in callback:', data);
            // Process pay-in data here
        } 
        // Return HTTP Response 200 with content "ok"
        res.status(200).send('ok');
    } else {
        // Invalid HMAC signature
        res.status(400).send('Invalid HMAC signature');
    }
});

// Start the server
app.listen(PORT, () => {
    console.log(`Server listening on port ${PORT}`);
});

```

{% endtab %}

{% tab title="Python" %}

```python
from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)

# Your TL Pay API Secret Key
TLP_API_SECRET_KEY = 'YOUR_TLP_API_SECRET_KEY'  # Replace with your actual API secret key

@app.route('/callback', methods=['POST'])
def callback():
    # Get the raw request data for HMAC calculation
    raw_data = json.dumps(request.get_json(), separators=(',', ':'), ensure_ascii=False)

    # Parse JSON data from the request
    data = request.get_json()
    
    # Retrieve the signature from the request headers
    tlp_signature = request.headers.get('X-TLP-SIGNATURE')

    # Calculate the HMAC SHA-256 signature
    calculated_hmac = hmac.new(
        key=TLP_API_SECRET_KEY.encode(),
        msg=raw_data,
        digestmod=hashlib.sha256
    ).hexdigest()

    # Compare the calculated HMAC signature with the one in the request header
    if hmac.compare_digest(calculated_hmac, tlp_signature):
        # Signature is valid
        if data['type'] == 'pay-in':
            print('Received pay-in callback:', data)
            # Process pay-in data here
            
        # Return HTTP Response 200 with content "ok"
        return jsonify({"message": "ok"}), 200
    else:
        # Invalid HMAC signature
        return jsonify({"error": "Invalid HMAC signature"}), 400

if __name__ == '__main__':
    app.run(port=3000, debug=True)

```

{% endtab %}
{% endtabs %}

Again, please note that these code snippets serve as examples and may require modifications based on your specific implementation and framework.

**Example of Web-hook Responses**

{% tabs %}
{% tab title="Pay-In: Success" %}

```json
{
  "data": {
    "orderId": "sample-id-1", // an id like ajdng-adgh-1433-adad
    "merchantOrderId": "sample-id-2", // an id like ajdng-adgh-1433-adad
    "baseAmount": 10,
    "baseCurrency": "USDT",
    "baseAmountReceived": 10,
    "settledCurrency": "USDT",
    "settledAmountRequested": 10,
    "settledAmountReceived": 10,
    "settledAmountCredited": 9.9,
    "commission": 0.1,
    "network": "BSC",
    "depositAddress": "address", // a crypto wallet address
    "status": "Completed",
    "paymentURL": "https://app.tylt.money/pscreen/sample-id-1", // sample-id-1 is the orderId above
    "callBackURL": "https://www.domain.com/your_callback_url", // your url where this callback data is sent
    "transactions": [
      {
        "fromAddress": "address", // a crypto wallet address
        "transactionHash": "txn_hash", // a crypto transaction hash
        "amount": 10,
        "confirmationStatus": 1,
        "createdAt": "2024-11-06T19:00:29Z",
        "updatedAt": "2024-11-06T19:01:21Z"
      }
    ],
    "createdAt": "2024-11-06T18:54:44Z",
    "expiresAt": "2024-11-06T19:54:44Z",
    "updatedAt": "2024-11-06T19:01:21Z",
    "isFinal": 1,
    "isCredited": 1,
    "customerName": "",
    "comments": "",
    "accounts" : {
      "rate": 90, // for transaction where a fiat equivalent crypto is required
      "cryptoReceived": 10,
      "equivalentFiat": 900// for transaction where a fiat equivalent crypto is required
    }
  },
  "type": "pay-in"
}
```

Again, please note that these response snippets serve as examples and may require modifications based on your specific implementation and framework.
{% endtab %}

{% tab title="Pay-Out: Success" %}

```json
{
  "data": {
    "orderId": "sample-id-1", // an id like ajdng-adgh-1433-adad
    "merchantOrderId": "sample-id-2", // an id like ajdng-adgh-1433-adad
    "settledCurrency": "USDT",
    "settledAmountRequested": 10,
    "settledAmountDebited": 10,
    "settledAmountSent": 9.9,
    "commission": 0.1,
    "network": "BSC",
    "toAddress": "address", // a crypto wallet address
    "status": "Completed",
    "insufficientBalance": 0,
    "paymentURL": "", // sample-id-1 is the orderId above
    "callBackURL": "", // your url where this callback data is sent
    "transactions": [
      {
        "fromAddress": "address", // a crypto wallet address
        "transactionHash": "txn_hash", // a crypto transaction hash
        "amount": 10,
        "confirmationStatus": 1,
        "createdAt": "2024-11-06T19:00:29Z",
        "updatedAt": "2024-11-06T19:01:21Z"
      }
    ],
    "createdAt": "2024-11-06T18:54:44Z",
    "expiresAt": "2024-11-06T19:54:44Z",
    "updatedAt": "2024-11-06T19:01:21Z",
    "isFinal": 1,
    "isDebited": 1,
    "customerName": "",
    "comments": "",
    "accounts" : {
      "rate": 90, // for transaction where a fiat equivalent crypto is required
      "cryptoSent": 10,
      "equivalentFiat": 900// for transaction where a fiat equivalent crypto is required
    }
  },
  "type": "pay-out"
}
```

Again, please note that these response snippets serve as examples and may require modifications based on your specific implementation and framework.
{% endtab %}
{% endtabs %}

### Understanding and Handling Transactions Based on Status

The response field **`status`** represents the current state of a transaction. Applications should interpret and handle transactions according to the following possible states:

| Status        | Description                                                                                                                           |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Pending       | The transaction is awaiting payment or confirmation.                                                                                  |
| Completed     | The transaction is successfully completed and settled. The customer has paid **exactly** the `settledAmountRequested`.                |
| Under Payment | **Applicable to Pay-in:** The transaction is completed and settled, but the customer paid **less** than the `settledAmountRequested`. |
| Over Payment  | **Applicable to Pay-in:** The transaction is completed and settled, but the customer paid **more** than the `settledAmountRequested`. |
| Expired       | **Applicable to Pay-in:** The transaction expired without any payment being received from the customer.                               |

## Understanding and Handling Over-Payment and Under-Payment

How a merchant handles over-payments and under-payments depends on their **business model**, **use case**, and **internal policies**. Broadly, practices differ between industries that **accept deposits** and those that **sell goods or services**.

***

### 1. Industries Accepting Deposits

*(e.g., iGaming, Trading, Forex, Wallet Services, Insurance)*

In these industries, payments are treated as **deposits into a user account/wallet**. The user’s balance is updated based on either:

* **baseCurrencyReceived** → The fiat or local equivalent value at the time of receipt (e.g., BRL, AED).
* **settledCurrencyReceived** → The actual crypto amount received in the settlement currency (e.g., USDT).

#### Under-Payment

* Merchant may accept the partial payment and **credit proportionally**.
* Merchant may choose to **refund the entire amount**.

{% hint style="warning" %}
**Refund can be initiated by using the** [**Creating a Payout Request API**](/tylt-cpg-crypto-asset-gateway/api-reference/transfer-crypto-assets/creating-a-payout-request.md)
{% endhint %}

#### Over-Payment

* Merchant may **refund the excess**.
* Merchant may accept the full amount and **credit the total received**.

{% hint style="warning" %}
**Refund can be initiated by using the** [**Creating a Payout Request API**](/tylt-cpg-crypto-asset-gateway/api-reference/transfer-crypto-assets/creating-a-payout-request.md)
{% endhint %}

***

**Example A: Deposit Requested in Crypto (USDT)**

* **Requested**: 100 USDT
* **Received**: 95 USDT (under-payment) → Merchant credits **95 USDT** to user wallet.
* **Received**: 105 USDT (over-payment) → Merchant credits **105 USDT** to user wallet.

Since both the **baseCurrency** and **settledCurrency** are the same (USDT), the merchant may use either `baseCurrencyReceived` or `settledCurrencyReceived` to handle the business logic.

***

**Example B: Deposit Requested in Fiat (FX-Denominated)**

* **Requested**: 500 BRL equivalent

1. **Received**: 95 USDT → At settlement, worth **475 BRL**.
   * Merchant may credit:
     * **475 BRL** (using `baseCurrencyReceived`), OR
     * **95 USDT** (using `settledCurrencyReceived`).
2. **Received**: 105 USDT → At settlement, worth **525 BRL**.
   * Merchant may credit:
     * **525 BRL** (using `baseCurrencyReceived`), OR
     * **105 USDT** (using `settledCurrencyReceived`).

{% hint style="warning" %}
**Response Records:**

* `baseCurrencyReceived = 475 BRL / 525 BRL`
* `settledCurrencyReceived = 95 USDT / 105 USDT`

This **dual recording** ensures flexibility: deposits can be credited in either **fiat terms** or **crypto terms**, depending on merchant policy.
{% endhint %}

***

### 2. Industries Accepting Payments for Sale of Merchandise

*(e.g., Retail, eCommerce, SaaS, Subscriptions)*

Here, payments correspond to a **specific invoice** for goods or services. Merchants may settle either in **fiat equivalent value** or in the **crypto amount received**.

#### Under-Payment

* Merchant may **hold the order** until the missing balance is paid.&#x20;
* Merchant may **accept partial payment** and adjust/store credit accordingly.

#### Over-Payment

* Merchant may **refund the excess amount**.
* Merchant may **apply the excess as store credit**.

{% hint style="warning" %}
**Refund can be initiated by using the** [**Creating a Payout Request API**](/tylt-cpg-crypto-asset-gateway/api-reference/transfer-crypto-assets/creating-a-payout-request.md)
{% endhint %}

***

#### 📌 Example A: Invoice Requested in Crypto (USDT)

* **Invoice**: 100 USDT

1. **Received = 95 USDT (under-payment)**

   * (a) Hold order until extra 5 USDT is received.
   * (b) Accept 95 USDT and adjust/store credit.

   **Response Records:**

   * `baseCurrencyReceived = 95 USDT`
   * `settledCurrencyReceived = 95 USDT`
2. **Received = 105 USDT (over-payment)**

   * (a) Ship order and refund 5 USDT.
   * (b) Apply 5 USDT as store credit.

   **Response Records:**

   * `baseCurrencyReceived = 105 USDT`
   * `settledCurrencyReceived = 105 USDT`

***

#### 📌 Example B: Invoice Requested in Fiat (AED)

* **Invoice**: 1,000 AED

1. **Received = 95 USDT → 950 AED (under-payment)**

   * (a) Hold order until missing 50 AED is received.
   * (b) Process order for 950 AED value.
   * (c) Refund the entire amount.

   **Response Records:**

   * `baseCurrencyReceived = 950 AED`
   * `settledCurrencyReceived = 95 USDT`
2. **Received = 105 USDT → 1,050 AED (over-payment)**

   * (a) Ship order for 1,000 AED and refund 50 AED.
   * (b) Apply 50 AED as store credit.
   * (c) Refund the entire 1,050 AED.

   **Response Records:**

   * `baseCurrencyReceived = 1,050 AED`
   * `settledCurrencyReceived = 105 USDT`

{% hint style="warning" %}
**Response Records:**

* `baseCurrencyReceived = 1,050 AED / 950 AED`
* `settledCurrencyReceived = 95 USDT / 105 USDT`

This **dual recording** ensures flexibility: deposits can be credited in either **fiat terms** or **crypto terms**, depending on merchant policy.
{% endhint %}


---

# Agent Instructions: 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:

```
GET https://docs.tylt.money/tylt-cpg-crypto-asset-gateway/api-reference/webhook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
