> 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-embedded-wallet-service/transactions/send-crypto.md).

# Send Crypto

### Create On-Chain Payout

Sends crypto from the end user’s wallet to an external blockchain address.

### Endpoint

```http
POST /whitelabel/wallet/payout
```

### Request Headers

```http
x-tlp-apikey: <api-key>
x-tlp-signature: <hmac-signature>
Content-Type: application/json
```

### Authorization Requirements

The request requires:

* Approved end-user KYC
* A transaction email OTP
* The end user’s Authenticator TOTP
* A valid timestamp
* A unique nonce
* Sufficient wallet balance
* A valid and supported destination address
* Any applicable wallet-screening or Travel Rule checks

### Request Fields

Provide at least one of the following user identifiers: `endUserEmail`, `endUserId`, or `externalUserId`.

| Field             | Type   |    Required | Description                                                      |
| ----------------- | ------ | ----------: | ---------------------------------------------------------------- |
| `endUserEmail`    | String | Conditional | End user’s email address                                         |
| `endUserId`       | Number | Conditional | Tylt's end-user ID                                               |
| `externalUserId`  | String | Conditional | Merchant’s own user identifier                                   |
| `emailCode`       | String |         Yes | Email OTP issued to the end user                                 |
| `googleAuthCode`  | String |         Yes | End user’s six-digit Authenticator code                          |
| `address`         | String |         Yes | The destination wallet address where the crypto needs to be sent |
| `settledAmount`   | String |         Yes | The amount of the crypto token to be sent                        |
| `settledCurrency` | String |         Yes | The crypto token to be sent.                                     |
| `networkSymbol`   | String |         Yes | The blockchain network to be used                                |
| `networkId`       | String |         Yee | The Id of the network                                            |
| `timestamp`       | Number |         Yes | Current epoch time in milliseconds                               |
| `nonce`           | String |         Yes | Unique request identifier                                        |

The request must also include the payout parameters required by Tylt’s underlying wallet payout API, including the destination, asset, network, and amount.

### Example Request Structure

```json
{
  "endUserId": 20001, // Optional: Provide one user identifier only
  "endUserEmail": "joe@example.com", // Optional: Alternative to endUserId and externalUserId
  "externalUserId": "user-10021", // Optional: Alternative to endUserId and endUserEmail
  "settledAmount": "10",
  "settledCurrency": "USDT",
  "address": "TTnUAQaRyhZQdJJZi4Zhc9saUbMqta7Gng",
  "networkId": "1",
  "networkSymbol": "TRX",
  "emailCode": "106856",
  "googleAuthCode": "502094",
  "timestamp": 1700000000000,
  "nonce": "9d893414-3ca7-47eb-aaba-3100f15cb4a1"
}
```

> The field names `destinationAddress`, `amount`, `currency`, and `network` must be confirmed against the deployed payout API schema.

### Recommended Payout Flow

1. Retrieve the end user’s available balance.
2. Collect the destination wallet address.
3. Ask the end user to select the token and blockchain network.
4. Validate the destination address.
5. Determine whether Travel Rule information is required.
6. Submit the required counterparty information.
7. Call the transaction OTP endpoint.
8. Collect the email OTP and Authenticator code.
9. Generate a timestamp and unique nonce.
10. Sign the complete request body.
11. Submit the payout request.
12. Display the transaction status to the end user.
13. Track the transaction using the transaction-history or transaction-details API.

### Code Snippet

{% tabs %}
{% tab title="JavaScript (Axios)" %}

```javascript
import crypto from 'crypto';
import axios from 'axios';

const apiKey = "your-api-key";
const apiSecret = "your-api-secret";

const requestBody = {
  endUserId: 20001, // Optional: Provide one user identifier only
  endUserEmail: "joe@example.com", // Optional: Alternative to endUserId and externalUserId
  externalUserId: "user-10021", // Optional: Alternative to endUserId and endUserEmail
  settledAmount: "10",
  settledCurrency: "USDT",
  address: "TTnUAQaRyhZQdJJZi4Zhc9saUbMqta7Gng",
  networkId: "1",
  networkSymbol: "TRX",
  emailCode: "106856",// OTP returned through the transaction email OTP flow
  googleAuthCode: "502094",// TOTP generated by the end user's authenticator application
  timestamp: Date.now(),
  nonce: crypto.randomBytes(8).toString("hex")
};

const raw = JSON.stringify(requestBody);

const signature = crypto
  .createHmac("sha256", apiSecret)
  .update(raw)
  .digest("hex");

const headers = {
  "x-tlp-apikey": apiKey,
  "x-tlp-signature": signature,
  "Content-Type": "application/json"
};

axios
  .post(
    "https://api.tylt.money/whitelabel/wallet/payout",
    raw,
    { headers }
  )
  .then((response) => {
    console.log(response.data);
  })
  .catch((error) => {
    console.error(
      "Error:",
      error.response ? error.response.data : error.message
    );
  });
```

{% endtab %}
{% endtabs %}

### Example Response

```json
{
  "data": {
    "transactionId": 2261800
  },
  "msg": "Withdrawal request accepted"
}
```

> The final response fields depend on the underlying payout controller.

### Transaction Status

An on-chain payout may pass through statuses such as:

| Status       | Description                                                      |
| ------------ | ---------------------------------------------------------------- |
| `pending`    | The request has been received and is being processed             |
| `processing` | The transaction is undergoing security or compliance checks      |
| `submitted`  | The transaction has been submitted to the blockchain             |
| `completed`  | The blockchain transaction has completed                         |
| `failed`     | The transaction could not be completed                           |
| `rejected`   | The transaction was rejected by a security or compliance control |

Actual status values may vary according to the deployed wallet API.

### Possible Errors

| HTTP Status | Message                                                            | Description                                |
| ----------- | ------------------------------------------------------------------ | ------------------------------------------ |
| `400`       | `Parameter emailCode is mandatory.`                                | Email OTP was not supplied                 |
| `400`       | `Parameter googleAuthCode is mandatory.`                           | Authenticator code was not supplied        |
| `400`       | `Access Denied! OTP does not match!`                               | Email OTP is incorrect or expired          |
| `400`       | `Unable to verify 2FA. Contact Support.`                           | TOTP enrollment or verification failed     |
| `400`       | `Parameters timestamp and nonce are mandatory for this operation.` | Replay-protection fields are missing       |
| `400`       | `Invalid signature.`                                               | Signature does not match the signed body   |
| `400`       | Validation error                                                   | Payout information is invalid              |
| `401`       | `Api Key authentication failed!`                                   | API authentication failed                  |
| `403`       | `API key or owner is inactive.`                                    | API access or merchant account is inactive |
| `403`       | `IP not whitelisted.`                                              | Request originated from an unauthorized IP |
| `403`       | `End user is suspended.`                                           | End-user account is suspended              |
| `403`       | KYC approval required                                              | End-user KYC is not approved               |
| `404`       | `End user not found for this owner.`                               | No matching end user was found             |
| `409`       | `Duplicate request detected (nonce already used).`                 | The nonce was previously used              |
| `422`       | Insufficient balance                                               | The user does not have sufficient funds    |
| `503`       | `Unable to verify IP whitelist.`                                   | Tylt could not verify the IP whitelist     |

***

## Recommended On-Chain Withdrawal Flow

A standard on-chain withdrawal should follow this sequence:

1. Confirm that the end user’s `walletOpsAllowed` status is `true`.
2. Retrieve the end user’s balance.
3. Collect the payout amount, asset, network, and destination address.
4. Validate the destination address.
5. Determine whether Travel Rule information is required.
6. Submit counterparty information through:

```http
POST /whitelabel/compliance/travelRule/addCounterparty
```

7. Request the transaction OTP through:

```http
GET /whitelabel/auth/sendTransactionOTP
```

8. Collect the email OTP and Authenticator code from the end user.
9. Generate the timestamp and nonce.
10. Sign the complete payout body.
11. Submit:

```http
POST /whitelabel/wallet/payout
```

12. Retrieve the transaction details or history until the transaction reaches a final status.

***
