Transfers: Overview

You can make transfers (also called payouts) individually or in bulk from your Flutterwave available balance. You can transfer to a bank account, a mobile money account, a Barter account, or another Flutterwave account.

This document explains a few things you need to know about transfers in general. You should read this first, and then you can view examples for specific types of transfers on their guide pages:

You can transfer money to the following countries with Flutterwave:

Nigeria, Ghana, Kenya, Uganda, Tanzania, South Africa, Zambia, Cameroon, Ivory Coast, Sierra Leone, Burkina Faso, Guinea-Bissau, Mali, Senegal, Rwanda, Tunisia, and Guinea Conakry.

See our support docs for details.

How transfers work

You can make a transfer from your dashboard or using our APIs (read more here).

Making a transfer via API looks like this. You make a POST request to our create transfer endpoint. The details will vary depending on the type of transfer, but you'll typically need to specify an amount, currency, account_bank, and account_number.

If you wish, you can also specify:

  • a narration describing the transfer,
  • a unique reference code to identify the transfer (if you don't supply one, we'll generate one for you), and
  • meta, for any extra information you want to record about the transfer, for example: "meta": {"quantity": "24", "goods_received_at": "24/02/21"}.
// Install with: npm i flutterwave-node-v3

const Flutterwave = require('flutterwave-node-v3');
const flw = new Flutterwave(process.env.FLW_PUBLIC_KEY, process.env.FLW_SECRET_KEY);
const details = {
    account_bank: "044",
    account_number: "0690000040",
    amount: 200,
    currency: "NGN",
    narration: "Payment for things",
    reference: generateTransactionReference(),
};
flw.Transfer.initiate(details)
    .then(console.log)
    .catch(console.log);
// Install with: composer require flutterwavedev/flutterwave-v3

$flw = new \Flutterwave\Rave(getenv('FLW_SECRET_KEY')); // Set `PUBLIC_KEY` as an environment variable
$transferService = new \Flutterwave\Transfer();
$details = [
    "account_bank" => "044",
    "account_number" => "0690000040",
    "amount" => 200,
    "narration" => "Payment for things",
    "currency" => "NGN",
    "reference" => generateTransactionReference(),
];
$response = $transferService->singleTransfer($details);
# Install with: gem install flutterwave_sdk

require 'flutterwave_sdk'

flw = Flutterwave.new(ENV["FLW_PUBLIC_KEY"], ENV["FLW_SECRET_KEY"], ENV["FLW_ENCRYPTION_KEY"])
transfer = Transfer.new(flw)
details = {
    account_bank: "044",
    account_number: "0690000040",
    amount: 200,
    narration: "Payment for things",
    currency: "NGN",
    reference: generate_transaction_reference,
}
response = transfer.initiate_transfer details
print response
# Install with: pip install rave_python

import os
from rave_python import Rave

rave = Rave(os.getenv("FLW_PUBLIC_KEY"), os.getenv("FLW_SECRET_KEY"))
details = {
    "account_bank": "044",
    "account_number": "0690000040",
    "amount": 200,
    "narration": "Payment for things",
    "currency": "NGN",
    "reference": generate_transaction_reference(),
}
res = rave.Transfer.initate(details)
print(res)
// Install with: go get github.com/Flutterwave/Rave-go/rave

import (
  "fmt"
  "os"
  "github.com/Flutterwave/Rave-go/rave"
)
var r = rave.Rave{
  false,
  os.Getenv("FLW_PUBLIC_KEY"),
  os.Getenv("FLW_SECRET_KEY"),
}
var transfer = rave.Transfer{
    r,
}
details := rave.SinglePaymentData {
    AccountBank:   "044",
    AccountNumber: "0690000044",
    Amount:        500,
    Narration:     "Payment for things",
    Currency:      "NGN",
    Reference:     GenerateTransactionReference(),
}
err, response := transfer.InitiateSingleTransfer(details)
if err != nil {
    panic(err)
}
fmt.Println(response)
curl --request POST 'https://api.flutterwave.com/v3/transfers' \
  --header 'Authorization: Bearer YOUR_SECRET_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "account_bank": "044",
    "account_number": "0690000040",
    "amount": 200,
    "narration": "Payment for things",
    "currency": "NGN",
    "reference": "jh678b3kol1Z"
}'
Cross-currency transfers

By default, we'll debit your balance matching the currency you're transferring to (for example, if you're transferring 200 GHS, we'll debit your GHS balance). You can override this by using the debit_currency field; for example, to transfer from your NGN balance to a GHS account:

await flw.Transfer.initiate({
    account_bank: "044",
    account_number: "0690000040",
    amount: 200,
    currency: "GHS",
    debit_currency: "NGN"
});

You can use the transfer rates endpoint to get the current rate we'll use when converting between currencies.

Handling the response

After creating a transfer, you'll get a response like this:

{
  "status": "success",
  "message": "Transfer Queued Successfully",
  "data": {
    "id": 190626,
    "account_number": "0690000040",
    "bank_code": "044",
    "full_name": "Alexis Sanchez",
    "created_at": "2021-04-26T11:19:55.000Z",
    "currency": "NGN",
    "debit_currency": "NGN",
    "amount": 200,
    "fee": 10.75,
    "status": "NEW",
    "reference": "jh678b3kol1Z",
    "meta": null,
    "narration": "Payment for things",
    "complete_message": "",
    "requires_approval": 0,
    "is_approved": 1,
    "bank_name": "ACCESS BANK NIGERIA"
  }
}
{
  "status": "error"
  "message": "Transfer creation failed"
  "data": {
    "id": 190874
    "account_number": "0690000040"
    "bank_code": "044"
    "full_name": "N/A"
    "created_at": "2021-04-28T14:49:42.000Z"
    "currency": "NGN"
    "debit_currency": "USD"
    "amount": 5500
    "fee": 26.875
    "status": "FAILED"
    "reference": "f2a321a89ab2c652"
    "meta": null,
    "narration": "Payment for goods purchased"
    "complete_message": "Beneficiary not found"
    "requires_approval": 0
    "is_approved": 1
    "bank_name": "N/A"
  }
}

There are some important details here:

  • data.id is the ID of the transfer. You can use this ID to fetch details about this transfer later.

  • data.status is the status of the transfer. data.complete_message is a friendly explanation of the status.

    • For a new transfer, the status is "NEW". The complete_message is typically empty.
    • A status of "PENDING" means that the transfer is being processed. The complete_message might be "Transaction is currently being processed".
    • When the transfer is completed, the status changes to "SUCCESSFUL". The complete_message might be "Transaction was successful".
    • If the transfer fails, the status changes to "FAILED". The complete_message would contain the failure reason, such as "Account resolve failed".

If a transfer fails, you can retry it with the retry transfer endpoint.

Checking the status of the transfer

There are three ways to find out the status of a transfer:

  1. If you have webhooks enabled, we'll call your webhook URL with the transfer details when the transfer is completed or fails.
  2. If you specified a callback_url when creating the transfer, we'll call that URL with the transfer details when the transfer is completed or fails.
    await flw.Transfer.initiate({
      account_bank: "044",
      account_number: "0690000040",
      amount: 200,
      currency: "NGN",
    + callback_url: 'http://my-callback-url'
    })
    
  3. You can manually check the current status of the transfer by calling the get transfer endpoint with the transfer ID.

Here's what the webhook/callback payload looks like:

{
  "event": "transfer.completed",
  "event.type": "Transfer",
  "data": {
    "id": 8416497,
    "account_number": "******",
    "bank_name": "ACCESS BANK NIGERIA",
    "bank_code": "044",
    "fullname": "John Doe",
    "created_at": "2021-04-28T17:01:41.000Z",
    "currency": "NGN",
    "debit_currency": "NGN",
    "amount": 100,
    "fee": 10.75,
    "status": "SUCCESSFUL",
    "reference": "TX-refe123456-6-3-1",
    "meta": null,
    "narration": "Test for wallet to wallet",
    "approver": null,
    "complete_message": "Transaction was successful",
    "requires_approval": 0,
    "is_approved": 1
  }
}
{
  "event": "transfer.completed",
  "event.type": "Transfer",
  "data": {
    "id": 8416559,
    "account_number": "********",
    "bank_name": "ACCESS BANK NIGERIA",
    "bank_code": "044",
    "fullname": "John Doe",
    "created_at": "2021-04-28T17:04:17.000Z",
    "currency": "NGN",
    "debit_currency": "NGN",
    "amount": 10000,
    "fee": 26.875,
    "status": "FAILED",
    "reference": "TX-refe123456-6-3-2",
    "meta": null,
    "narration": "Test for wallet to wallet",
    "approver": null,
    "complete_message": "DISBURSE FAILED: Insufficient funds in customer wallet",
    "requires_approval": 0,
    "is_approved": 1
  }
}

Here's how to check using the get transfer endpoint:

const response = await flw.Transfer.get_a_transfer({id: transferId});
response = transfer.get_a_transfer transfer_id
curl --request GET 'https://api.flutterwave.com/v3/transfers/85623' \
  --header 'Authorization: Bearer YOUR_SECRET_KEY'

Checking the transfer fee

After making a transfer, the fee you were charged is indicated in the fee field of the response. You can check how much you'll be charged before making the transfer by using the get transfer fee endpoint. You'll need to specify the amount, currency, and type of transfer (mobilemoney, barter, or account).

Transfers to other Flutterwave merchants are free.

For example, if you wish to transfer 2700 EUR to a bank account, you'd make this request:

const response = await flw.Transfer.fee({
  type: "account",
  amount: 2700,
  currency: "EUR",
});
$response = $transferService->getTransferFee([
    "amount" => 2700,
    "type" => "account",
    "currency" => "EUR",
]);
response = transfer.transfer_fee {
    type: "account",
    amount: 2700,
    currency: "EUR",
}
curl --request GET 'https://api.flutterwave.com/v3/transfers/fee' \
  --header 'Authorization: Bearer YOUR_SECRET_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
      "type": "account",
      "amount": 2700,
      "currency": "EUR"
}'

You'll get a response like this:

{
  "status": "success",
  "message": "Transfer fee fetched",
  "data": [
    {
      "currency": "EUR",
      "fee_type": "value",
      "fee": 35
    }
  ]
}

This means you'll be charged 35 EUR for this transfer.

Beneficiaries

After making a transfer, the destination account details are saved as a new beneficiary. You can then use the beneficiary ID as a shortcut to make a transfer to the same account in the future, allowing you to skip specifying the account details.

await flw.Transfer.initiate({
+   beneficiary: 7758,
    amount: 200,
    currency: "NGN",
});
$transferService->singleTransfer([
+   "beneficiary" => 7758,
    "amount" => 200,
    "currency" => "NGN",
]);
transfer.initiate_transfer({
+   beneficiary: 7758,
    amount: 200,
    currency: "NGN",
})
rave.Transfer.initate({
+   "beneficiary": 7758,
    "amount": 200,
    "currency": "NGN",
})
transfer.InitiateSingleTransfer(rave.SinglePaymentData {
+   Beneficiary:   7758,
    Amount:        500,
    Currency:      "NGN",
})
curl --request POST 'https://api.flutterwave.com/v3/transfers' \
  --header 'Authorization: Bearer YOUR_SECRET_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
+   "beneficiary": 7758,
    "amount": 200,
    "currency": "NGN"
}'

You can manage your saved beneficiaries with the API: you can add beneficiaries at any time, fetch one or all beneficiary details, and delete a beneficiary.

Verifying account details

You can fetch a destination account's details before making a transfer. While this isn't required, it can be helpful to confirm that the details were entered correctly, and the transfer is going to the intended recipient.

Verification is supported for Nigerian bank accounts, and Flutterwave merchant IDs. See our guide to bank account verification for details.

Testing transfers

When testing a transfer, you can use any of our provided test accounts. By default, such transfers will always remain in a PENDING state. You can force transfers to behave differently by using a special kind of transaction reference when creating the transfer.

  • To mock a successful transfer: Make sure your reference ends with _PMCK (for example, "dfs23fhr7ntg0293039_PMCK".
  • To mock a failed transfer: Make sure your reference ends with _PMCK_ST_F (for example, "dfs23fhr7ntg0293039_PMCK_ST_F".

By default, the status of the mocked transfer will only be updated after 10 minutes.

  • To change the time delay: append DU_{minutes} to your transaction reference.

For example:

  • a transfer with a reference of "dfs23fhr7ntg0293039_PMCK" will succeed after 10 minutes
  • a transfer with a reference of "dfs23fhr7ntg0293039_PMCKDU_1" will succeed after 1 minute
  • a transfer with a reference of "dfs23fhr7ntg0293039_PMCK_ST_F" will fail after 10 minutes
  • a transfer with a reference of "dfs23fhr7ntg0293039_PMCK_ST_FDU_1" will fail after 1 minute

Learn more

If you're stuck, check out our support documentation on transfers.

Loading...