> For the complete documentation index, see [llms.txt](https://docs.tylt.money/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tylt.money/tylt-crossramp-fiat-crypto-solutions/canada-intrac/canada-interac-e-transfer-on-ramp/webhook-for-tylt-crossramp-pay-in.md).

# Webhook for Tylt CrossRamp (Pay-in)

***

### Overview

Tylt sends real-time transaction-status notifications to the `callBackUrl` supplied when the CAD pay-in instance is created.

A webhook is sent whenever the payment instance moves from one lifecycle state to another.

The webhook should be used as the primary mechanism for receiving transaction updates. Merchants may use the Get Instance Information endpoint to independently confirm the latest transaction status.

***

### Setting Up the Webhook

#### 1. Create a Callback Endpoint

The merchant must provide a publicly accessible HTTPS endpoint that:

* Accepts HTTP `POST` requests.
* Accepts a JSON request body.
* Preserves the original raw request body.
* Validates the `X-TLP-SIGNATURE` header.
* Processes each status update idempotently.
* Returns HTTP status `200` with the text `ok` in the response body.

#### 2. Submit the Callback URL

Include the callback endpoint in the `callBackUrl` field when creating the pay-in instance.

```json
{
  "callBackUrl": "https://merchant.example/webhooks/tylt/cad"
}
```

#### 3. Process Status Updates

Each webhook contains an `eventId` identifying the latest transaction state.

The merchant should perform customer crediting, order fulfilment or other irreversible actions only after receiving and validating `eventId: 5`.

***

### Webhook Headers

| Header            | Description                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `Content-Type`    | `application/json`                                                                                   |
| `X-TLP-SIGNATURE` | HMAC-SHA256 signature generated from the exact raw webhook body using the merchant’s API Secret Key. |
| `sign`            | Legacy signature header retained for compatibility.                                                  |

***

### Webhook Reponses ( Event Id: 1 through Event Id: 12)

{% tabs %}
{% tab title="Event Id:1" %}

#### **1. Instance Created (eventId: 1)**

Triggered when a payment instance is created. The step initiates the payment and settlement flow.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": null,
    "eventDetails": {
      "eventId": 1,
      "description": "Instance Created"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": null,
      "hash": null,
      "address": null,
      "Network": "TPNK",
      "type": "internal"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:2" %}

#### **2. Order Created / Payment Pending (eventId: 2)**

Triggered with an interac payment link is generated and the payment is pending.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 2,
      "description": "Order Created / Payment Pending"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": null,
      "hash": null,
      "address": null,
      "Network": "TPNK",
      "type": "internal"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:3" %}

#### &#x20;**Payment Processing (eventId: 3)**

Triggered when the payment is initiated by the end-user and the payment is under processing.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 3,
      "description": "Payment Processing"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": null,
      "hash": null,
      "address": null,
      "Network": "TPNK",
      "type": "internal"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:4 " %}

#### **4. Payment Fulfilled (eventId: 4)**

Triggered when the payment is initiated by the end-user and the status is fullfiled. This status is not an intermediate step and does not signify completed or finality of the fiat payment.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 4,
      "description": "Payment Fulfilled"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": null,
      "hash": null,
      "address": null,
      "Network": "TPNK",
      "type": "internal"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:5" %}

#### **5. Payment Completed (eventId: 5)**

Triggered when the fiat payment by the end-user has been completed and settled.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 5,
      "description": "Payment Completed"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": null,
      "hash": null,
      "address": null,
      "Network": "TPNK",
      "type": "internal"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:6" %}

#### **6. Settlement Pending (eventId: 6)**

Triggered when the fiat payment is completed and the on-chain settlement to the end-user wallet has been initiated. This process is triggered only at the settlement Time (T+1 etc) is elapsed.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 6,
      "description": "Settlement Pending"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "settlementPending",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

<br>
{% endtab %}

{% tab title="Event Id:7" %}

#### **7. Settlement Completed (eventId: 7)**

Triggered directly via internal withdrawal sequences dispensing crypto natively. `hash` attaches.

```json
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 7,
      "description": "Settlement Completed"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "settlementCompleted",
      "hash": "0x4fb812c3b88b7bc2d...",
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:8" %}

#### **8. Payment Failed (eventId: 8)**

Triggered if the payment process fails or the settlement process fails.&#x20;

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 8,
      "description": "Payment Failed / Declined"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "failed",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id:9" %}
**9. Order Expired (eventId: 9)**

Triggered when the Interac Payment Link expires due to non payment by the end-user. The link expires 48 hours after issuance.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 9,
      "description": "Order Expired"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "expired",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id: 10" %}

#### **10. Refund Initiated (eventId: 10)**

Triggered actively when a manual admin refund is iniated.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 10,
      "description": "Refund Initiated"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "refundInitiated",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id: 11" %}
**11. Refund Completed (eventId: 11)**

Hook triggers directly upon verifying the Interac refund securely reaching the user.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 11,
      "description": "Refund Completed"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "refunded",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}

{% tab title="Event Id 12" %}

#### **12. KYC Failed (eventId: 12)**

Strictly triggered when the KYC processor natively responds with a formal rejection on the ID tier mappings for the specific instance user trace.

```json
{
  "data": {
    "instanceId": "992a5752-89ad-4ef6-af6e-67bd858ef1d7",
    "isBuying": 1,
    "callBackUrl": "https://dev-api.tylt.money/common/postback",
    "merchantOrderId": "cadtest1786097846972",
    "interacUrl": "https://gateway-web.fit.interac.ca/acceptPaymentRequest.do?rID=CA1XyzRef&src=email",
    "eventDetails": {
      "eventId": 12,
      "description": "KYC Failed"
    },
    "accounts": {
      "fiatCurrency": "CAD",
      "fiatAmount": 1000,
      "cryptoCurrency": "USDC",
      "cryptoAmount": 712.25,
      "toReleaseAmount": 710.43,
      "effectiveRate": 1.4975,
      "rate": 1.4042,
      "fees": 1.82,
      "MDR": 6
    },
    "cryptoSettlementDetails": {
      "Status": "kycFailed",
      "hash": null,
      "address": "0xDestinationWallet123",
      "Network": "TPNK",
      "type": "external"
    }
  }
}
```

{% endtab %}
{% endtabs %}

***

### Webhook Fields

| Field                                  | Type           | Description                                                                                                                                |
| -------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `data.instanceId`                      | string         | Unique pay-in instance identifier generated by Tylt.                                                                                       |
| `data.isBuying`                        | number         | Indicates transaction direction. `1` represents a pay-in / on-ramp transaction.                                                            |
| `data.callBackUrl`                     | string         | Callback URL configured for receiving transaction-status webhooks.                                                                         |
| `data.merchantOrderId`                 | string         | Unique transaction reference supplied by the merchant.                                                                                     |
| `data.interacUrl`                      | string or null | Interac payment URL associated with the transaction, where applicable. May be `null` before the Interac request is generated.              |
| `data.eventDetails`                    | object         | Current transaction lifecycle information.                                                                                                 |
| `data.eventDetails.eventId`            | number         | Numeric identifier representing the current transaction state.                                                                             |
| `data.eventDetails.description`        | string         | Human-readable description of the current transaction state.                                                                               |
| `data.accounts`                        | object         | Fiat, crypto, rate, fee and settlement-amount details for the transaction.                                                                 |
| `data.accounts.fiatCurrency`           | string         | Fiat currency used for the transaction. For Canada pay-ins, this is `CAD`.                                                                 |
| `data.accounts.fiatAmount`             | number         | Amount paid or to be paid by the end user in CAD.                                                                                          |
| `data.accounts.cryptoCurrency`         | string         | Crypto-asset used for settlement, such as `USDC` or `USDT`.                                                                                |
| `data.accounts.cryptoAmount`           | number         | Gross crypto amount calculated from the fiat amount using the applicable conversion rate.                                                  |
| `data.accounts.toReleaseAmount`        | number         | Net crypto amount to be released or credited after applicable fees and transaction adjustments.                                            |
| `data.accounts.effectiveRate`          | number         | Effective conversion rate applicable after incorporating the commercial pricing or spread applied to the transaction.                      |
| `data.accounts.rate`                   | number         | Base CAD-to-crypto conversion rate used for the transaction calculation.                                                                   |
| `data.accounts.fees`                   | number         | Total transaction fees applied to the transaction, expressed in the settlement asset unless otherwise configured.                          |
| `data.accounts.MDR`                    | number         | Merchant Discount Rate applied to the transaction.                                                                                         |
| `data.cryptoSettlementDetails`         | object         | Details relating to settlement of the crypto asset.                                                                                        |
| `data.cryptoSettlementDetails.Status`  | string or null | Current crypto settlement status. May be `null` before settlement is initiated.                                                            |
| `data.cryptoSettlementDetails.hash`    | string or null | Blockchain transaction hash for external settlement. `null` for transactions not yet settled or where no on-chain transaction is required. |
| `data.cryptoSettlementDetails.address` | string or null | Destination wallet address for external settlement. May be `null` for internal settlement.                                                 |
| `data.cryptoSettlementDetails.Network` | string or null | Blockchain or internal network identifier associated with settlement.                                                                      |
| `data.cryptoSettlementDetails.type`    | string         | Settlement type. Typically `internal` or `external`.                                                                                       |

***

### Validating the Webhook Signature

Tylt signs the exact raw JSON webhook body using HMAC-SHA256 and the merchant’s API Secret Key.

```
expectedSignature = HMAC-SHA256(
    API_SECRET_KEY,
    RAW_WEBHOOK_BODY
)
```

The resulting lowercase hexadecimal digest is sent in the `X-TLP-SIGNATURE` header.

The merchant must:

1. Read the exact raw request body.
2. Generate an HMAC-SHA256 signature using the API Secret Key.
3. Compare the generated signature with `X-TLP-SIGNATURE`.
4. Use a constant-time comparison.
5. Reject the webhook when the signatures do not match.
6. Parse and process the JSON only after successful validation.

> Do not parse and re-serialise the JSON before generating the signature. Changes to spacing, field order or encoding may change the calculated signature.

***

### Node.js Webhook Example

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

const app = express();
const apiSecret = process.env.TYLT_API_SECRET;

// Preserve the exact raw body.
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/tylt/cad", (req, res) => {
  const receivedSignature =
    req.headers["x-tlp-signature"] || "";

  const expectedSignature = crypto
    .createHmac("sha256", apiSecret)
    .update(req.body)
    .digest("hex");

  const receivedBuffer = Buffer.from(
    receivedSignature,
    "utf8"
  );

  const expectedBuffer = Buffer.from(
    expectedSignature,
    "utf8"
  );

  const signatureIsValid =
    receivedBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(
      receivedBuffer,
      expectedBuffer
    );

  if (!signatureIsValid) {
    return res
      .status(400)
      .send("Invalid HMAC signature");
  }

  const webhook = JSON.parse(
    req.body.toString("utf8")
  );

  const {
    instanceId,
    merchantOrderId,
    eventDetails
  } = webhook.data;

  // Store and process the event idempotently.
  console.log({
    instanceId,
    merchantOrderId,
    eventId: eventDetails.eventId
  });

  return res.status(200).send("ok");
});

app.listen(3000, () => {
  console.log("Webhook server running");
});
```

***

### Acknowledging the Webhook

After successfully validating and recording the webhook, return:

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

```
ok
```

The callback is acknowledged only when Tylt receives HTTP status `200` and the response body `ok`.

Webhooks are not automatically retried when an acknowledgement is not received. Missed webhooks may be manually resent through the Tylt dashboard.

***

### Webhook Processing Requirements

* Always validate `X-TLP-SIGNATURE`.
* Match the `instanceId` and `merchantOrderId` against the merchant’s records.
* Store and process webhook events idempotently.
* Do not assume that every intermediate event will always be received.
* Do not credit the customer based on an intermediate status.
* Treat `eventId: 5` (Payment Completed),`eventId: 6` (Settlement Pending), and`eventId: 7` (Settlement Completed)as successful states. `7` is the ultimate terminal success state.
* Treat event IDs`8` (Failed), `9` (Expired), `10` (Refund Initiated), and `12` (KYC Failed)as terminal unsuccessful states.
* Use the Get Instance Information endpoint to verify an uncertain or missing status.
* Store the webhook body, signature, event ID, processing result and timestamp for reconciliation.
