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

# Integration

# Transfers (Pay-Outs)

The *Transfer API (Pay-Out API)* is quite simple to implement, as there is no interaction with end customers.

* [Transfers (Pay-Outs)](#transfers-pay-outs)
  * [Making a transfer](#making-a-transfer)
    * [Restrictions](#restrictions)
  * [Retrieving transfer details](#retrieving-transfer-details)
  * [Retrieving transfer status](#retrieve-a-transfer-status)
  * [List transfers](#list-transfers)
    * [Pagination](#pagination)

***

## Making a transfer

[HUB2 API reference - Create a transfer](/api-reference/transfers/create-transfer)

Performing a transfer only requires one call to the dedicated endpoint.

Sample request :

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request POST 'https://api.hub2.io/transfers' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "reference": "<YOUR_INTERNAL_REFERENCE>",
    "amount": 2000,
    "currency": "XOF",
    "description": "<YOUR_DESCRIPTION>",
    "destination": {
      "type": "mobile_money",
      "country": "CI",
      "recipientName": "John Doe",
      "msisdn": "+225000000000",
      "provider": "orange"
    }
  }'
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function postTransfer() {
      const response = await fetch('https://api.hub2.io/transfers', {
          method: 'POST',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox',
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              "reference": "<YOUR_INTERNAL_REFERENCE>",
              "amount": 2000,
              "currency": "XOF",
              "description": "<YOUR_DESCRIPTION>",
              "destination": {
                  "type": "mobile_money",
                  "country": "CI",
                  "recipientName": "John Doe",
                  "msisdn": "+225000000000",
                  "provider": "orange"
              }
          })
      });

      if (response.ok) {
          const result = await response.json();
          console.log(result);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  postTransfer();
  ```

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

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
      'Content-Type': 'application/json',
  }

  data = {
      "reference": "<YOUR_INTERNAL_REFERENCE>",
      "amount": 2000,
      "currency": "XOF",
      "description": "<YOUR_DESCRIPTION>",
      "destination": {
          "type": "mobile_money",
          "country": "CI",
          "recipientName": "John Doe",
          "msisdn": "+225000000000",
          "provider": "orange"
      }
  }

  response = requests.post(
      'https://api.hub2.io/transfers',
      headers=headers,
      data=json.dumps(data)
  )

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

This request will return with a transfer ID, which is the unique identifier of the transfer. An unique ID is created on the HUB2 platform for each transfer request.

<Warning>
  **Transfer ID must be saved in the merchant's database for the next steps.**
</Warning>

<Note>
  For any ticket to HUB2 support team concerning a transfer, the transfer ID will be asked, not the merchant's reference.
</Note>

### Restrictions

When creating a transfer, checks are performed on the field `reference` : no special characters are allowed. Trying to use special characters here will result in a `400 Bad Request` error.

Allowed characters are letters, numbers, hyphen, underscore, dot and space. Here's the list in the regular expression format : `A-Za-z0-9\-_. `.

***

## Retrieving transfer details

[HUB2 API reference - Get transfer details](/api-reference/transfers/retrieve-a-transfer)

To retrieve a transfer, perform a call to the dedicated endpoint. *Note : This endpoint requires a transfer ID, obtained when making a transfer ([see previous step](#making-a-transfer)).*

This endpoint was designed more to get the full details of a transfer, *rather than to be only used to check for transfer status changes*.
***This endpoint***, like every endpoint on HUB2 API, ***is rate limited***. That means that if this endpoint is called too many times by a merchant ID, the HUB2 API will respond with `HTTP 429 Too Many Requests` to that merchant ID.

<Note>
  Documentation about rate limits [can be found here](/documentation/en/limits).
</Note>

Sample request :

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request GET 'https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox' \
  --header 'Content-Type: application/json' \
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function getTransfer() {
      const response = await fetch('https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx', {
          method: 'GET',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox',
              'Content-Type': 'application/json'
          }
      });

      if (response.ok) {
          const transfer = await response.json();
          console.log(transfer);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  getTransfer();
  ```

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

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
      'Content-Type': 'application/json',
  }

  response = requests.get(
      'https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx',
      headers=headers
  )

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

Once a Transfer has been created and webhooks have been configured to receive notifications about the Transfer's lifecycle, HUB2 will send webhooks for each event associated with configured webhooks.

**This is the recommended method for taking into account changes in the status of a Transfer.** Some Transfers are processed quickly, while others less so, for a variety of unforeseeable reasons. There is no point in retrieving the details of a Transfer to retrieve the status of a Transfer if no change has taken place. This is why setting up webhooks is recommended.

<Note>
  Documentation about webhooks [can be found here](/integration/en/webhooks/webhooks_overview).
</Note>

***

## Retrieve a transfer status

<Tip>
  This feature was added February the 4th of 2025.
</Tip>

As the previous endpoint was not meant to retrieve a transfer status because of performance reasons, a new dedicated endpoint was added specifically for that purpose.

[HUB2 API reference - Retrieve a transfer status](/api-reference/transfers/retrieve-a-transfers-status)

This endpoint is still rate limited but the rate limit settings will be lower than the one above.

Sample request :

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request GET 'https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx/status' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox' \
  --header 'Content-Type: application/json' \
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function getTransfer() {
      const response = await fetch('https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx/status', {
          method: 'GET',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox',
              'Content-Type': 'application/json'
          }
      });

      if (response.ok) {
          const transfer = await response.json();
          console.log(transfer);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  getTransfer();
  ```

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

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
      'Content-Type': 'application/json',
  }

  response = requests.get(
      'https://api.hub2.io/transfers/tr_xxxxxxxxxxxxxxxxxx/status',
      headers=headers
  )

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

*If any trouble is encountered while implementing the webhooks mechanism, this endpoint could be used to fetch the status of a transfer instead.*

***

## List transfers

[HUB2 API reference - List transfers](/api-reference/transfers/retrieve-a-transfer-collection)

To fetch a list of transfers, use the dedicated endpoint. Several query parameters (see below) can be applied to filter our results.

Sample request :

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request GET 'https://api.hub2.io/transfers?from=2023-01-01T00:00:00.000&to=2023-01-01T12:00:00.000&page=1&perPage=100' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox' \
  --header 'Content-Type: application/json' \
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function getTransfers() {
      const response = await fetch('https://api.hub2.io/transfers?from=2023-01-01T00:00:00.000&to=2023-01-01T12:00:00.000&page=1&perPage=100', {
          method: 'GET',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox',
              'Content-Type': 'application/json'
          }
      });

      if (response.ok) {
          const transfers = await response.json();
          console.log(transfers);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  getTransfers();
  ```

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

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
      'Content-Type': 'application/json',
  }

  response = requests.get(
      'https://api.hub2.io/transfers?from=2023-01-01T00:00:00.000&to=2023-01-01T12:00:00.000&page=1&perPage=100',
      headers=headers
  )

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

Please note the following parameters:

* **Parameter `from` :** identifies the starting date of the transfers retrieval range, the value here is `2023-01-01T00:00:00.000Z`
* **Parameter `to` :** identifies the ending date of the transfers retrieval range, the value here is `2023-01-01T12:00:00.000Z`
* **Parameters `page` and `perPage` :** To control navigation through the result pages.

<Note>
  By default, when no filter is defined, **the 100 last transfers are returned** by this endpoint, ordered by `created_at`, descending.
</Note>

<Warning>
  **For a big number of transfers, paginated requests will be mandatory to retrieve all transactions from the HUB2 API.**
</Warning>

### Pagination

Pagination is available on that endpoint.

The response headers provide the `Content-Range` header to inform of the total number of results, and the header value is in the format `0-99/2453`.

In this particular case, this header indicates that the API returned the first 100 results out of a total of 2453 - so there are 25 pages of 100 results to fetch to retrieve all transfers corresponding to the initial filter.

A good advice is to set date filters in the initial request, so the number of results remains the same while pagination is running (except when `to` is set to a date in the future).
