# Webhook for Payout Requests

#### Overview

Tylt provides a webhook mechanism for merchants to receive real-time updates on the status of their payout requests,  Merchants can specify a `callBackUrl`  by navigating to the Bulk Payouts> Add Callback URL on the Tylt Application.

{% hint style="warning" %}
Webhook updates are triggered for each **change in state of an individual payout request**, not for the overall payout instance.

To track the status or updates of a **payout instance**, please use the appropriate API endpoint provided below
{% endhint %}

#### 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. **Status Updates:**  The life cycle of a payment instance is tracked via `event`. Below is the list of possible `event` values and their meanings:

| Event Name | Description                                                                                              |
| ---------- | -------------------------------------------------------------------------------------------------------- |
| created    | Payout request created                                                                                   |
| initiated  | Payout request has been initiated and awaiting further processing                                        |
| processing | Payout request is being processed. Transaction is currently in progress and being handled by the system. |
| pending    | Payout request has completed processing and is awaiting  status confirmation                             |
| completed  | Payout request has completed  processing successfully and the UTR confirmation is returned.              |
| failed     | Payout request could not be processed and the transaction failed.                                        |
| deleted    | Payout deleted                                                                                           |

3. **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`.
4. **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
        // Process pay-out 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

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 = request.data

    # 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
        # Process pay-out 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="created" %}

```json
{
    "utr": null,
    "event": "created",
    "amount": 100,
    "createdAt": "2025-07-11T09:55:16Z",
    "feeAmount": 0.03,
    "requestId": "6fe4513b-20c9-484c-b18c-34abf92b40a2",
    "secretKey": "4f3d34b6f2c86fe9b223bdd69a497f443a1f67637c29ef8bb9ef5d1c4ab17482",
    "updatedAt": "2025-07-11T09:55:21Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test",
    "beneficiaryIFSC": "SBIN0001895",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "38556507709"
}
```

{% endtab %}

{% tab title="initiated" %}

```json
{
    "utr": null,
    "event": "initiated",
    "amount": 100,
    "createdAt": "2025-07-11T09:55:16Z",
    "feeAmount": 0.03,
    "requestId": "6fe4513b-20c9-484c-b18c-34abf92b40a2",
    "secretKey": "4f3d34b6f2c86fe9b223bdd69a497f443a1f67637c29ef8bb9ef5d1c4ab17482",
    "updatedAt": "2025-07-11T09:55:21Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test",
    "beneficiaryIFSC": "SBIN0001895",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "38556507709"
}
```

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

{% tab title="processing" %}

```json
{
    "utr": null,
    "event": "processing",
    "amount": 100,
    "createdAt": "2025-07-11T09:55:16Z",
    "feeAmount": 0.03,
    "requestId": "6fe4513b-20c9-484c-b18c-34abf92b40a2",
    "secretKey": "4f3d34b6f2c86fe9b223bdd69a497f443a1f67637c29ef8bb9ef5d1c4ab17482",
    "updatedAt": "2025-07-11T09:56:01Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test",
    "beneficiaryIFSC": "SBIN0001895",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "38556507709"
}
```

{% endtab %}

{% tab title="pending" %}

```json
{
    "utr": null,
    "event": "pending",
    "amount": 100,
    "createdAt": "2025-07-11T09:34:36Z",
    "feeAmount": 0.03,
    "requestId": "51bde81d-2798-421b-a486-fda1fb3ba097",
    "secretKey": "4f3d34b6f2c86fe9b223bdd69a497f443a1f67637c29ef8bb9ef5d1c4ab17482",
    "updatedAt": "2025-07-11T10:08:06Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test",
    "beneficiaryIFSC": "SBIN0001895",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "38556507709"
}
```

{% endtab %}

{% tab title="failed" %}

```javascript
{
    "utr": null,
    "event": "failed",
    "amount": 100,
    "createdAt": "2025-07-11T09:34:36Z",
    "feeAmount": 0.03,
    "requestId": "51bde81d-2798-421b-a486-fda1fb3ba097",
    "secretKey": "4f3d34b6f2c86fe9b223bdd69a497f443a1f67637c29ef8bb9ef5d1c4ab17482",
    "updatedAt": "2025-07-11T10:08:06Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test",
    "beneficiaryIFSC": "SBIN0001895",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "38556507709"
}
```

{% endtab %}

{% tab title="completed" %}

```json
{
    "utr": "519245660157",
    "event": "completed",
    "amount": 100,
    "createdAt": "2025-07-11T13:12:31Z",
    "feeAmount": 0.03,
    "requestId": "dde5e645-fa26-484e-8c54-b3e97ace867b",
    "updatedAt": "2025-07-11T13:15:06Z",
    "cryptoAmount": 1.16,
    "merchantRefId": "merchant-ref-test-xlsx-2",
    "beneficiaryIFSC": "KKBK0000328",
    "beneficiaryName": "Akshat Agarwal",
    "fiatCurrencySymbol": "INR",
    "cryptoCurrencySymbol": "USDT",
    "beneficiaryAccountNumber": "0715708673"
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Important Considerations**

* **Security:** Always verify the `X-TLP-SIGNATURE` header to ensure the callback originates from Tylt.
* **Response:** Always return an HTTP 200 response with `"ok"` in the body to acknowledge successful receipt of the web-hook.
* **Manual Retry:** In case of missed callbacks, use the tylt.money dashboard to manually resend the webhook.
  {% endhint %}
