> 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/internal-transfer.md).

# Internal Transfer

**Create Internal Transfer**

Transfers crypto from one end user to another end user belonging to the same merchant.

Internal transfers are processed within the Tylt platform and do not require an on-chain blockchain transaction.

### Endpoint

```http
POST /whitelabel/wallet/internalTransfer
```

### Request Headers

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

### Sender Identification

Identify the sender using one of:

| Field            | Description                          |
| ---------------- | ------------------------------------ |
| `endUserEmail`   | Sender’s registered email address    |
| `endUserId`      | Sender’s Tylt end-user ID            |
| `externalUserId` | Merchant’s identifier for the sender |

The merchant must not submit `fromUUID`.

Tylt derives the sender’s internal UUID after resolving and validating the user.

### Recipient Identification

Identify the recipient using one of:

| Field              | Description                             |
| ------------------ | --------------------------------------- |
| `toExternalUserId` | Merchant’s identifier for the recipient |
| `toEndUserId`      | Recipient’s Tylt end-user ID            |
| `toEndUserEmail`   | Recipient’s registered email address    |
| `toUUID`           | Recipient’s platform UUID               |

The recommended recipient identifier is:

```
toExternalUserId
```

`toUUID` is accepted only when the UUID belongs to another user provisioned under the same merchant.

### Request Parameters

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

| Field              | Type             |    Required | Description                        |
| ------------------ | ---------------- | ----------: | ---------------------------------- |
| `externalUserId`   | String           | Conditional | Merchant’s own user identifier     |
| `endUserId`        | Number           | Conditional | Tylt's end user Id                 |
| `endUserEmail`     | String           | Conditional | End user’s registered email        |
| `toExternalUserId` | String or Number |         Yes | Recipient identifier               |
| `settledAmount`    | String           |         Yes | Amount to transfer                 |
| `settledCurrency`  | String           |         Yes | Asset or currency to transfer      |
| `comments`         | String           |          No | Transfer description or comment    |
| `timestamp`        | Number           |         Yes | Current epoch time in milliseconds |
| `nonce`            | String           |         Yes | Unique request identifier          |
| `emailCode`        | String           |         Yes | Email OTP issued to the sender     |
| `googleAuthCode`   | String           |         Yes | Sender’s Authenticator code        |

### Example Request

```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
  "toExternalUserId": "recipient-user-10045",
  "settledAmount": "10",
  "settledCurrency": "USDT",
  "comments": "Payment for order 4501",
  "timestamp": 1700000000000,
  "nonce": "ecb0272c-637e-4778-b4d4-55e6eed21017",
  "emailCode": "123456",// OTP returned through the transaction email OTP flow
  "googleAuthCode": "654321"// TOTP generated by the end user's authenticator application
}
```

The complete body must be signed exactly as submitted.

### 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
    toExternalUserId: 'recipient-999', // Recipient's external ID (mapped server-side to UUID)
    settledAmount: '5',
    settledCurrency: 'USDT',
    comments: 'Internal Transfer Payment',
    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/internalTransfer', 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
{
    "msg": "",
    "data": {
      "transactionId": 2261778,
      "toTransactionId": 2261779,
      "transactionType": "personal",
      "toTransactionType": "personal"
    }
  }
```

> The final response structure depends on the underlying internal-transfer controller.

### How Internal Transfers Work

1. Tylt resolves the sender under the authenticated merchant.
2. Tylt confirms that the sender is active and KYC-approved.
3. Tylt resolves the recipient under the same merchant.
4. Tylt confirms that the recipient is active.
5. Tylt verifies the sender’s email OTP and Authenticator code.
6. Tylt verifies the sender’s available balance.
7. The sender’s balance is debited.
8. The recipient’s balance is credited.
9. The transfer appears in both users’ transaction histories.

### Internal Transfer Restrictions

* The sender and recipient must belong to the same merchant.
* The sender and recipient must be different users.
* The recipient must not be suspended.
* The merchant cannot transfer funds to a user belonging to another merchant.
* The sender must have sufficient available balance.
* The transferred asset must be supported for internal transfers.

### Possible Errors

| HTTP Status | Message                                                                        | Description                                        |
| ----------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
| `400`       | `One of toExternalUserId, toEndUserId, toEndUserEmail, or toUUID is required.` | No recipient identifier was provided               |
| `400`       | `Sender and recipient must be different accounts.`                             | The same user was supplied as sender and recipient |
| `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 invalid or expired                    |
| `400`       | `Unable to verify 2FA. Contact Support.`                                       | TOTP verification failed                           |
| `400`       | `Parameters timestamp and nonce are mandatory for this operation.`             | Replay-protection fields are missing               |
| `401`       | `Api Key authentication failed!`                                               | API authentication failed                          |
| `403`       | `Recipient is suspended.`                                                      | The recipient cannot receive transfers             |
| `403`       | `End user is suspended.`                                                       | The sender is suspended                            |
| `403`       | KYC approval required                                                          | The sender has not completed KYC                   |
| `404`       | `Recipient not found for this owner.`                                          | The recipient does not belong to the merchant      |
| `404`       | `End user not found for this owner.`                                           | The sender was not found                           |
| `409`       | `Duplicate request detected (nonce already used).`                             | The nonce was previously used                      |
| `422`       | Insufficient balance                                                           | The sender does not have sufficient funds          |

##
