> ## Documentation Index
> Fetch the complete documentation index at: https://developer.nomba.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Transfer to banks

> Learn how to perform bank transfers using the Nomba API

<CardGroup cols={2}>
  <Card title="Perform bank transfers from the parent account" icon="shuffle" href="/nomba-api-reference/transfers/perform-bank-account-transfer-from-the-parent-account" />

  <Card title="Perform bank transfers from the sub-account" icon="wallet" href="/nomba-api-reference/transfers/perform-bank-account-transfer-from-the-sub-account" />
</CardGroup>

# `POST /v2/transfers/bank`

> ⚠️ **Rate Limit Notice:**\
> Users are restricted to **5 bank transfers to the same recipient per minute**.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.nomba.com/v2/transfers/bank \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --header 'accountId: <accountid>' \
    --data '{
      "amount": 3500,
      "accountNumber": "055472814",
      "accountName": "M.A Animashaun",
      "bankCode": "058",
      "merchantTxRef": "UNQ_123abGGhh5546",
      "senderName": "Nightly Post",
      "narration": "Nice One"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.nomba.com/v2/transfers/bank', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'accountId': accountId,
    },
    body: JSON.stringify({
      amount: 3500,
      accountNumber: '055472814',
      accountName: 'M.A Animashaun',
      bankCode: '058',
      merchantTxRef: 'UNQ_123abGGhh5546',
      senderName: 'Nightly Post',
      narration: 'Nice One',
    }),
  });

  const { code, data } = await response.json();
  if (code !== '00') throw new Error(`Transfer failed: ${code}`);

  if (data.status === 'PENDING_BILLING') {
    // Poll or wait for webhook with data.id
  } else if (data.status === 'REFUND') {
    throw new Error('Transfer refunded. Safe to retry.');
  }
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.nomba.com/v2/transfers/bank',
      headers={
          'Authorization': f'Bearer {access_token}',
          'Content-Type': 'application/json',
          'accountId': account_id,
      },
      json={
          'amount': 3500,
          'accountNumber': '055472814',
          'accountName': 'M.A Animashaun',
          'bankCode': '058',
          'merchantTxRef': 'UNQ_123abGGhh5546',
          'senderName': 'Nightly Post',
          'narration': 'Nice One',
      },
  )

  result = response.json()
  if result['code'] != '00':
      raise Exception(f"Transfer failed: {result['code']}")

  status = result['data']['status']
  if status == 'PENDING_BILLING':
      pass  # Poll or wait for webhook with result['data']['id']
  elif status == 'REFUND':
      raise Exception('Transfer refunded. Safe to retry.')
  ```

  ```json Response theme={null}
  {
    "code": "200",
    "description": "SUCCESS",
    "message": "Success",
    "status": true,
    "data": {
      "id": "API-TRANSFER-11EC4-45990eb5-7b0f-4845-87f8-xxxxxxxxxxxx",
      "status": "PENDING_BILLING",
      "type": "transfer",
      "amount": 1.0,
      "source": "api",
      "sourceUserId": "11ec45a1-1fe5-44f5-8baf-cxxxxxxxxxx",
      "customerBillerId": "010784xxxx",
      "productId": "058",
      "meta": {
        "api_rrn": "25120913xxxxx",
        "narration": "Testing",
        "recipientName": "John Doe",
        "sender_name": "Nightly Post",
        "rrn": "251209131319",
        "api_account_id": "670353d1-158a-4257-8f0b-xxxxxxxxxxx",
        "api_client_id": "651ab1c3-c703-4cc3-86ac-xxxxxxxxxxx",
        "user_id": "670353d1-158a-4257-8f0b-xxxxxxxxxxx",
        "merchantTxRef": "testingTxV2ForMerchant",
        "allowDuplicate": true,
        "idempotentKey": "670353d1-158a-4257-8f0b-d06d1fed3c35_testixxxxxxxxxxx",
        "pos_withdrawal_id": "670353d1-158a-4257-8f0b-d06d1fed3c35_testingxxxxxxxxxxx",
        "userName": "Code Trik Nigeria Enterprises",
        "isCorporate": "true",
        "currency": "NGN",
        "hooksEligible": "true",
        "banking_entity_id": 19787017,
        "banking_entity_user_id": 19787014,
        "banking_entity_type": "CORPORATE",
        "self_transaction": true,
        "transactionCategory": "Family & Kids",
        "accountNumber": "0107841806",
        "bankName": "GTBank",
        "bankCode": "058",
        "sessionId": "",
        "user_referral_code": "HABI76702",
        "amount_charged": "21.0",
        "paymentVendor": "wallet",
        "wallet_balance": "5.38",
        "wallet_currency": "NGN",
        "paymentVendorReference": "6938125f6e98e9ace8df850e",
        "agent_commission": "0.0",
        "useV2Fulfilment": "true"
      },
      "userId": "670353d1-158a-4257-8f0b-xxxxxxxxxx",
      "timeCreated": "2025-12-09 12:13:19"
    }
  }
  ```
</CodeGroup>

# `POST /v2/transfers/bank/{subAccountId}`

If your usecase requires transferring funds from a specific sub-account, you can use the sub-account transfer endpoint.
This is useful where you manage multiple balances or wallets under your main account.

Sub-accounts can only be created from your Nomba dashboard.

> 📢 **Feature Notice:**\
> Sub-account transfers must be enabled by us before you can use this endpoint.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.nomba.com/v2/transfers/bank/{subAccountId} \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --header 'accountId: <accountid>' \
    --data '{
      "amount": 3500,
      "accountNumber": "055472814",
      "accountName": "M.A Animashaun",
      "bankCode": "058",
      "merchantTxRef": "UNQ_123abGGhh5546",
      "senderName": "Nightly Post",
      "narration": "Nice One"
    }'
  ```

  ```javascript Node.js theme={null}
  const subAccountId = 'your-sub-account-id';

  const response = await fetch(
    `https://api.nomba.com/v2/transfers/bank/${subAccountId}`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        'accountId': accountId,
      },
      body: JSON.stringify({
        amount: 3500,
        accountNumber: '055472814',
        accountName: 'M.A Animashaun',
        bankCode: '058',
        merchantTxRef: 'UNQ_123abGGhh5546',
        senderName: 'Nightly Post',
        narration: 'Nice One',
      }),
    }
  );

  const { code, data } = await response.json();
  if (code !== '00') throw new Error(`Transfer failed: ${code}`);
  ```

  ```python Python theme={null}
  import requests

  sub_account_id = 'your-sub-account-id'

  response = requests.post(
      f'https://api.nomba.com/v2/transfers/bank/{sub_account_id}',
      headers={
          'Authorization': f'Bearer {access_token}',
          'Content-Type': 'application/json',
          'accountId': account_id,
      },
      json={
          'amount': 3500,
          'accountNumber': '055472814',
          'accountName': 'M.A Animashaun',
          'bankCode': '058',
          'merchantTxRef': 'UNQ_123abGGhh5546',
          'senderName': 'Nightly Post',
          'narration': 'Nice One',
      },
  )

  result = response.json()
  if result['code'] != '00':
      raise Exception(f"Transfer failed: {result['code']}")
  ```

  ```json Response theme={null}
  {
    "code": "200",
    "description": "SUCCESS",
    "message": "Success",
    "status": true,
    "data": {
      "id": "API-TRANSFER-11EC4-45990eb5-7b0f-4845-87f8-xxxxxxxxxxxx",
      "status": "PENDING_BILLING",
      "type": "transfer",
      "amount": 1.0,
      "source": "api",
      "sourceUserId": "11ec45a1-1fe5-44f5-8baf-cxxxxxxxxxx",
      "customerBillerId": "010784xxxx",
      "productId": "058",
      "meta": {
        "api_rrn": "25120913xxxxx",
        "narration": "Testing",
        "recipientName": "John Doe",
        "sender_name": "Nightly Post",
        "rrn": "251209131319",
        "api_account_id": "670353d1-158a-4257-8f0b-xxxxxxxxxxx",
        "api_client_id": "651ab1c3-c703-4cc3-86ac-xxxxxxxxxxx",
        "user_id": "670353d1-158a-4257-8f0b-xxxxxxxxxxx",
        "merchantTxRef": "testingTxV2ForMerchant",
        "allowDuplicate": true,
        "idempotentKey": "670353d1-158a-4257-8f0b-d06d1fed3c35_testixxxxxxxxxxx",
        "pos_withdrawal_id": "670353d1-158a-4257-8f0b-d06d1fed3c35_testingxxxxxxxxxxx",
        "userName": "Code Trik Nigeria Enterprises",
        "isCorporate": "true",
        "currency": "NGN",
        "hooksEligible": "true",
        "banking_entity_id": 19787017,
        "banking_entity_user_id": 19787014,
        "banking_entity_type": "CORPORATE",
        "self_transaction": true,
        "transactionCategory": "Family & Kids",
        "accountNumber": "0107841806",
        "bankName": "GTBank",
        "bankCode": "058",
        "sessionId": "",
        "user_referral_code": "HABI76702",
        "amount_charged": "21.0",
        "paymentVendor": "wallet",
        "wallet_balance": "5.38",
        "wallet_currency": "NGN",
        "paymentVendorReference": "6938125f6e98e9ace8df850e",
        "agent_commission": "0.0",
        "useV2Fulfilment": "true"
      },
      "userId": "670353d1-158a-4257-8f0b-xxxxxxxxxx",
      "timeCreated": "2026-03-08T12:13:19Z"
    }
  }
  ```
</CodeGroup>

## **Understanding Transfer Responses & Lifecycle**

When you call the transfer endpoint `POST /v2/transfers/bank`, you will immediately receive a response if your request is valid (e.g., sufficient balance, correct payload structure, etc.).

#### **Immediate Response Status**

You will receive a successful API response with one of the following values in `data.status`

**Example 1 — Transfer immediately successful**

```json theme={null}
{
   "successful": true,
  "status": "SUCCESS",
  "message": "Success",
  "data": {
    "id": "API-TRANSFER-02145-XXXXX-8857-417f-a954-1234",
    "status": "SUCCESS"
  }
}
```

**Example 2 — Transfer processing**

```json theme={null}
{
  "successful": true,
  "status": "SUCCESS",
  "message": "Success",
  "data": {
    "id": "API-TRANSFER-02415-XXXX-8857-417f-1234",
    "status": "PENDING_BILLING"
  }
}
```

**Example 3 — Transfer processing**

```json theme={null}
{
  "successful": true,
  "status": "SUCCESS",
  "message": "Success",
  "data": {
    "id": "API-TRANSFER-02415-XXXX-8857-417f-1234",
    "status": "NEW"
  }
}
```

#### **What `data.status` Means**

* SUCCESS: Transfer completed successfully
* PENDING\_BILLING: Transfer is being processed and will soon be completed
* NEW: Transfer is being processed and will be completed

#### **Tracking Transaction Status**

<Steps>
  <Step title="Wait for Webhook (Recommended)"> Once the transaction is completed, a webhook notification will be sent to your system. </Step>
  <Step title="Poll for Status (Optional)"> If you prefer polling, use the transaction ID (`data.id`) returned in the response to query the transaction status. </Step>
</Steps>

#### Requery Endpoints

* If you are transferring from a parent account, use [this](/nomba-api-reference/requery/fetch-a-single-transaction-on-the-parent-account)

```bash theme={null}
GET /v1/transactions/accounts/single?transactionRef=API-TRANSFER-XXX-XXX
```

* If you are transferring from a sub account, use [this](/nomba-api-reference/requery/fetch-a-single-transaction-on-a-sub-account)

```bash theme={null}
GET /v1/transactions/accounts/{subAccountId}/single?transactionRef=API-TRANSFER-XXX-XXX

```

* If you use both parent and sub account to carry out transfers, use [this](/nomba-api-reference/requery/transaction-requery)

```bash theme={null}
GET /v1/transactions/transaction-requery/{sessionId}
```

<Info title="Additional Information">
  * Transactions **may not** be immediately available to requery (e.g., within 1 second).
  * Some transactions may take up to **3 minutes** due to NIBSS processing delays.
  * If you love polling, use **interval-based retries (up to \~3 minutes and few secs)**.
  * Nomba-to-Nomba transfers **do not include a sessionId** use the parent or sub-account endpoints for requery.
</Info>

#### **Handling a 201 Response**

A `201` HTTP status code means the transfer request was received but the final outcome is not yet available. The transaction will be processed.

```json theme={null}
{
  "code": "201",
  "description": "PROCESSING",
  "message": "Unable to process response, please rely on web hook",
  "status": false,
  "data": {
    "status": "PENDING_BILLING"
  }
}
```

When you receive a 201, you should:

1. Mark the transaction as pending in your system
2. Keep the original `merchantTxRef` — do not generate a new one
3. Wait for the webhook notification for the final status (`SUCCESS` or `REFUND`)
4. Use the requery endpoints if you need to poll for status rather than waiting for the webhook

#### **Handling Failed Transactions**

* If a transaction fails, your account will be automatically refunded and a refund webhook notification will be sent.
* If you love polling, query the endpoint like we previously discussed, look out for `data.status = REFUND`. It means the transaction failed and has been refunded.
* You can safely retry the transaction after a refund using a new `merchantTxRef`.

```json theme={null}
{
  "code": "00",
  "description": "SUCCESS",
  "status": false,
  "data": {
    "id": "API-TRANSFER-02415-XXXX-8857-417f-1234",
    "status": "REFUND"
  }
}
```

#### **How To Prevent Double Disbursement**

Funds transfers are sensitive. To prevent duplicate payouts, follow these best practices:

**1. Use Idempotent Transaction References**

* Always send a unique `merchantTxRef` per transaction
* Reuse the same reference when retrying a request

**2. Handle Pending Responses Correctly**

* If a transaction returns `PENDING`, do NOT re-initiate the transfer with a different reference
* The transaction may still be processed successfully

**3. Retry Safely**

* Only retry using the same `merchantTxRef`
* Do NOT generate a new reference for the same transaction, if it is still pending

**4. Verify Transaction Status**

* Use the Requery endpoint discussed earlier to confirm the final status
* Do not rely solely on the initial response

**5. Use Webhooks (Recommended)**

* Subscribe to [webhook](docs/api-basics/webhook) notifications for real-time updates
* Webhooks provide the final and authoritative transaction status

**6. When in Doubt**

* Treat unknown responses as `PENDING`
* If you receive an unexpected status or code, pause and contact us before retrying
* A list of all status codes can be found [here](/nomba-api-reference/transfers/perform-bank-account-transfer-from-the-parent-account)

#### **Summary**

* Every transaction is being processed
* We have a retry mechanism to ensure it is successful
* A refund will happen immediately it fails.

#### Request body

<ParamField body="amount" type="number" required>
  The amount to be transferred.
</ParamField>

<ParamField body="accountNumber" type="string" required>
  The destination bank account number.
</ParamField>

<ParamField body="accountName" type="string" required>
  The name on the account.
</ParamField>

<ParamField body="bankCode" type="string" required>
  The code of the recipient bank.
</ParamField>

<ParamField body="merchantTxRef" type="string" required>
  <p>Unique reference used to track a transaction from an external process.</p>

  <p>
    This is an idempotency key and must be unique per transaction. It cannot be reused once a transfer has been initiated.
  </p>
</ParamField>

<ParamField body="senderName" type="string" required>
  The sender's name.
</ParamField>

<ParamField body="narration" type="string">
  The narration for this transfer (NB: This will be appended to the normal system generated narration).
</ParamField>

#### Response body

<ResponseField name="id" type="string" required>
  The transfer ID.
</ResponseField>

<ResponseField name="status" type="string" required>
  The transaction status.
</ResponseField>

<ResponseField name="type" type="string" required>
  The transaction type.
</ResponseField>

<ResponseField name="amount" type="number" required>
  The transfer amount.
</ResponseField>

<ResponseField name="fee" type="number">
  The transfer fee.
</ResponseField>

<ResponseField name="source" type="string">
  Payment source (e.g., “api”).
</ResponseField>

<ResponseField name="sourceUserId" type="string">
  The user who initiated the transfer.
</ResponseField>

<ResponseField name="customerBillerId" type="string">
  The biller account ID.
</ResponseField>

<ResponseField name="productId" type="string">
  Product code for the transfer.
</ResponseField>

<ResponseField name="meta" type="object" required>
  Additional transaction metadata.

  <Expandable title="object" defaultOpen="true">
    <ResponseField name="merchantTxRef" type="string">
      Merchant transaction reference.
    </ResponseField>

    <ResponseField name="api_client_id" type="string">
      API client ID.
    </ResponseField>

    <ResponseField name="api_account_id" type="string">
      API account ID.
    </ResponseField>

    <ResponseField name="rrn" type="string">
      Retrieval Reference Number.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="userId" type="string">
  Associated user ID.
</ResponseField>

<ResponseField name="timeCreated" type="string" required>
  Creation timestamp.
</ResponseField>
