Single Transfers

In A Nutshell
In a nutshell
With the Paystack Transfer API, you can send money to bank accounts and mobile money wallet

You can transfer money in four easy steps:

  1. Verify the account number
  2. Create a transfer recipient
  3. Initiate a transfer
  4. Listen for transfer status
Before you begin!
To send money on Paystack, you need API keys to authenticate your transfers. You can find your keys on the Paystack Dashboard under Settings → API Keys & Webhooks.

Verify the account number

You need to collect the customer's account details and confirm that it’s valid before sending money. This is to ensure you don’t send money to the wrong or invalid account. Kindly check out the Verify Account Number page to learn about how to verify account details in different regions.

List banks and telcos

When creating a transfer recipient, you need the bank or telco details of the customer. The list bankAPI can be used to get all supported banks and telcos in a country.

To fetch a list of banks in a country, make a GET request passing the currency in the query parameter:

Show Response
1curl https://api.paystack.co/bank?currency=NGN
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-X GET
1{
2 "status": true,
3 "message": "Banks retrieved",
4 "data": [
5 {
6 "name": "Abbey Mortgage Bank",
7 "slug": "abbey-mortgage-bank",
8 "code": "801",
9 "longcode": "",
10 "gateway": null,
11 "pay_with_bank": false,
12 "active": true,
13 "is_deleted": false,
14 "country": "Nigeria",
15 "currency": "NGN",
16 "type": "nuban",
17 "id": 174,
18 "createdAt": "2020-12-07T16:19:09.000Z",
19 "updatedAt": "2020-12-07T16:19:19.000Z"
20 },
21 ...
22 ]
23}

To fetch a list of supported telcos for mobile money, you can add currency and type in the query parameters for the list bankAPI:

Show Response
1curl https://api.paystack.co/bank?currency=GHS&type=mobile_money
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-X GET
1{
2 "status": true,
3 "message": "Banks retrieved",
4 "data": [
5 {
6 "name": "AirtelTigo",
7 "slug": "atl-mobile-money",
8 "code": "ATL",
9 "longcode": "",
10 "gateway": null,
11 "pay_with_bank": false,
12 "active": true,
13 "is_deleted": null,
14 "country": "Ghana",
15 "currency": "GHS",
16 "type": "mobile_money",
17 "id": 29,
18 "createdAt": "2018-03-29T12:54:59.000Z",
19 "updatedAt": "2020-01-24T10:01:06.000Z"
20 },
21 {
22 "name": "MTN",
23 "slug": "mtn-mobile-money",
24 "code": "MTN",
25 "longcode": "",
26 "gateway": null,
27 "pay_with_bank": false,
28 "active": true,
29 "is_deleted": null,
30 "country": "Ghana",
31 "currency": "GHS",
32 "type": "mobile_money",
33 "id": 28,
34 "createdAt": "2018-03-29T12:54:59.000Z",
35 "updatedAt": "2019-10-22T11:04:46.000Z"
36 },
37 {
38 "name": "Vodafone",
39 "slug": "vod-mobile-money",
40 "code": "VOD",
41 "longcode": "",
42 "gateway": null,
43 "pay_with_bank": false,
44 "active": true,
45 "is_deleted": null,
46 "country": "Ghana",
47 "currency": "GHS",
48 "type": "mobile_money",
49 "id": 66,
50 "createdAt": "2018-03-29T12:54:59.000Z",
51 "updatedAt": "2019-10-22T11:05:08.000Z"
52 }
53 ]
54}

Create a transfer recipient

Before sending money to an account, you need to create a transfer recipient with the customer’s account details.

The recipient type can take the following values based on country:

TypeCurrencyDescription
nubanNGNThis means the Nigerian Uniform Bank Account Number. It represents bank accounts in Nigeria.
mobile_moneyGHSThis means Ghana Interbank Payment and Settlement Systems. It represents bank account in Ghana
basaZARThis means the Banking Association South Africa. It represents bank accounts in South Africa
authorizationAllThis is a unique code that represents a customer’s card. We return an authorization code after a user makes a payment with their card.

To create the transfer recipient, make a POST request to the transfer recipientAPI passing one of the following customer’s detail:

  1. Bank account
  2. Mobile money
  3. Authorization code

Bank account

Here, you specify the type based on your integration currency, then pass the customer’s details:

Show Response
1curl https://api.paystack.co/transferrecipient
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "type": "nuban",
5 "name": "John Doe",
6 "account_number": "0001234567",
7 "bank_code": "058",
8 "currency": "NGN"
9 }'
10-X POST
1{
2 "status": true,
3 "message": "Transfer recipient created successfully",
4 "data": {
5 "active": true,
6 "createdAt": "2020-05-13T13:59:07.741Z",
7 "currency": "NGN",
8 "domain": "test",
9 "id": 6788170,
10 "integration": 428626,
11 "name": "John Doe",
12 "recipient_code": "RCP_t0ya41mp35flk40",
13 "type": "nuban",
14 "updatedAt": "2020-05-13T13:59:07.741Z",
15 "is_deleted": false,
16 "details": {
17 "authorization_code": null,
18 "account_number": "0001234567",
19 "account_name": null,
20 "bank_code": "058",
21 "bank_name": "Guaranty Trust Bank"
22 }
23 }
24}

Mobile money

With mobile money, the bank_code takes the telco code as its value and the mobile number is passed to the account_number parameter:

Show Response
1curl https://api.paystack.co/transferrecipient
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "type": "mobile_money",
5 "name": "Abina Nana",
6 "account_number": "0551234987",
7 "bank_code": "MTN",
8 "currency": "GHS"
9 }'
10-X POST
1{
2 "status": true,
3 "message": "Transfer recipient created successfully",
4 "data": {
5 "active": true,
6 "createdAt": "2022-02-21T12:57:02.156Z",
7 "currency": "GHS",
8 "domain": "test",
9 "id": 25753454,
10 "integration": 519035,
11 "name": "Abina Nana",
12 "recipient_code": "RCP_u2tnoyjjvh95pzm",
13 "type": "mobile_money",
14 "updatedAt": "2022-02-21T12:57:02.156Z",
15 "is_deleted": false,
16 "isDeleted": false,
17 "details": {
18 "authorization_code": null,
19 "account_number": "0551234987",
20 "account_name": null,
21 "bank_code": "MTN",
22 "bank_name": "MTN"
23 }
24 }
25}

Authorization code

An authorization code is returned after a successful card payment by a customer. Combining the authorization code with the email address used for payment, you can create a transfer recipient:

Show Response
1curl https://api.paystack.co/transferrecipient
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "type": "authorization",
5 "name": "Revs Ore",
6 "email": "revs@ore.com",
7 "authorization_code": "AUTH_ncx8hews93"
8 }'
9-X POST
1{
2 "status": true,
3 "message": "Transfer recipient created successfully",
4 "data": {
5 "active": true,
6 "createdAt": "2022-02-21T11:35:59.302Z",
7 "currency": "NGN",
8 "domain": "test",
9 "email": "revs@ore.com",
10 "id": 25747878,
11 "integration": 463433,
12 "name": "Revs Ore",
13 "recipient_code": "RCP_wql6bj95bll7m6h",
14 "type": "authorization",
15 "updatedAt": "2022-02-21T11:35:59.302Z",
16 "is_deleted": false,
17 "isDeleted": false,
18 "details": {
19 "authorization_code": "AUTH_ncx8hews93",
20 "account_number": null,
21 "account_name": null,
22 "bank_code": "057",
23 "bank_name": "Zenith Bank"
24 }
25 }
26}
Account Number Association
If an account number isn’t associated with the authorization code, we return a response with a message: Account details not found for this authorization. If you get this error, kindly request the customer’s account details and follow the process to create a transfer recipient using a bank account.

The recipient_code from the data object is the unique identifier for this user and would be used to make transfers to the specified account. This code should be saved with the customer's records in your database.

Initiate a transfer

To send money to a customer, you make a POST request to the Initate TransferAPI, passing the recipient_code previously created.

Disabling OTP
When building a fully automated system, you might need to disable OTP for transfers. You can disable OTP from the Preferences tab on the Paystack Dashoard. You should uncheck the Confirm transfers before sending checkbox as shown in the image below.
Image of the disabled OTP state of tranfers
Show Response
1curl https://api.paystack.co/transfer
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "source": "balance",
5 "amount": "3794800",
6 "recipient": "RCP_t0ya41mp35flk40",
7 "reason": "Holiday Flexing"
8 }'
9-X POST
1{
2 "status": true,
3 "message": "Transfer has been queued",
4 "data": {
5 "reference": "on5hyz9poe",
6 "integration": 428626,
7 "domain": "test",
8 "amount": 3794800,
9 "currency": "NGN",
10 "source": "balance",
11 "reason": "Holiday Flexing",
12 "recipient": 6788170,
13 "status": "success",
14 "transfer_code": "TRF_fiyxvgkh71e717b",
15 "id": 23070321,
16 "createdAt": "2020-05-13T14:22:49.687Z",
17 "updatedAt": "2020-05-13T14:22:49.687Z"
18 }
19}

When you send this request, if there are no errors, the response comes back with a pending status, while the transfer is being processed.

Retrying a transfer
If there is an error with the transfer request, kindly retry the transaction with the same reference in order to avoid double crediting. If a new reference is used, the transfer would be treated as a new request.

Test transfers always return success, because there is no processing involved. The live transfers processing usually take between a few seconds and a few minutes. When it's done processing, a notification is sent to your webhook URL.

Listen for transfer status

Receiving Notifications
In order to receive notifications, you need to implement a webhook URL and set the webhook URL on your Paystack Dashboard.

When a transfer is initiated, it could take a few seconds or minutes to process. Once processed, Paystack sends the final status of the transfer as a POST request to your webhook URL.

EventDescription
transfer.successThis is sent when the transfer is successful
transfer.failedThis is sent when the transfer fails
transfer.reversedThis is sent when we refund a previously debited amount for a transfer that couldn’t be completed
1{
2 "event": "transfer.success",
3 "data": {
4 "amount": 30000,
5 "currency": "NGN",
6 "domain": "test",
7 "failures": null,
8 "id": 37272792,
9 "integration": {
10 "id": 463433,
11 "is_live": true,
12 "business_name": "Boom Boom Industries NG"
13 },
14 "reason": "Have fun...",
15 "reference": "1jhbs3ozmen0k7y5efmw",
16 "source": "balance",
17 "source_details": null,
18 "status": "success",
19 "titan_code": null,
20 "transfer_code": "TRF_wpl1dem4967avzm",
21 "transferred_at": null,
22 "recipient": {
23 "active": true,
24 "currency": "NGN",
25 "description": "",
26 "domain": "test",
27 "email": null,
28 "id": 8690817,
29 "integration": 463433,
30 "metadata": null,
31 "name": "Jack Sparrow",
32 "recipient_code": "RCP_a8wkxiychzdzfgs",
33 "type": "nuban",
34 "is_deleted": false,
35 "details": {
36 "account_number": "0000000000",
37 "account_name": null,
38 "bank_code": "011",
39 "bank_name": "First Bank of Nigeria"
40 },
41 "created_at": "2020-09-03T12:11:25.000Z",
42 "updated_at": "2020-09-03T12:11:25.000Z"
43 },
44 "session": { "provider": null, "id": null },
45 "created_at": "2020-10-26T12:28:57.000Z",
46 "updated_at": "2020-10-26T12:28:57.000Z"
47 }
48}
49

The response for a transfer also contains a unique transfer code to identify this transfer. You can use this code to call the Fetch TransferAPI endpoint to fetch status and details of the transfer anytime you want.

Authenticating with OTP

As an additional layer of security, a Paystack business owner might want to confirm a transfer by using the One Time Password (OTP) sent to their device.

If you have OTP enabled on your Paystack Dashboard and you intiate a transfer via the API, you get a response with message: Transfer requires OTP to continue and data.status: otp

1{
2 "status": true,
3 "message": "Transfer requires OTP to continue",
4 "data": {
5 "reference": "x8beye1xud",
6 "integration": 463433,
7 "domain": "test",
8 "amount": 20000,
9 "currency": "NGN",
10 "source": "balance",
11 "reason": "E choke",
12 "recipient": 10751086,
13 "status": "otp",
14 "transfer_code": "TRF_1aqrqhoomp0rjro",
15 "id": 56506855,
16 "createdAt": "2021-04-07T12:41:57.763Z",
17 "updatedAt": "2021-04-07T12:41:57.763Z"
18 }
19}

When the business owner supplies the OTP sent to their mobile number (and email if enabled), the transfer can be completed by using our Finalize TransferAPI endpoint:

Show Response
1curl https://api.paystack.co/transfer/finalize_transfer
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "transfer_code": "TRF_vsyqdmlzble3uii", "otp": "928783" }'
5-X POST
1{
2 "status": true,
3 "message": "Transfer has been queued",
4 "data": {
5 "reference": "on5hyz9poe",
6 "integration": 428626,
7 "domain": "test",
8 "amount": 3794800,
9 "currency": "NGN",
10 "source": "balance",
11 "reason": "Holiday Flexing",
12 "recipient": 6788170,
13 "status": "success",
14 "transfer_code": "TRF_vsyqdmlzble3uii",
15 "id": 23070321,
16 "createdAt": "2020-05-13T14:22:49.687Z",
17 "updatedAt": "2020-05-13T14:22:49.687Z"
18 }
19}