Bulk Transfers

In A Nutshell
In a nutshell

Send money to multiple recipients at once with Paystack bulk transfer API

Some business models require sending money to multiple customers at the same time. For example, a payroll management system requires sending salaries to all employees at a certain time of the month. Large disbursements such as this requires careful orchestration in order to ensure that all customers get paid and your system doesn’t get overwhelmed.

The Bulk TransferAPI endpoint handles large disbursement orchestration, helping businesses focus on delivering value to their customers.

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.

Creating recipients

A transfer recipient is a beneficiary on your integration that you can send money to. Before sending money to your customers, you need to collect their details first, then use their details to create a transfer recipient. We support different recipients in different countries:

TypeDescriptionCurrency
nubanThis means the Nigerian Uniform Bank Account Number. It represents bank accounts in Nigeria.NGN
mobile_moneyMobile Money or MoMo is an account tied to a mobile numberGHS
basaThis means the Banking Association South Africa. It represents bank accounts in South AfricaZAR
authorizationThis is a unique code that represents a customer’s card. We return an authorization code after a user makes a payment with their card.All

Once created, save the recipient_code to your DB and make it available for the transfer initiation. This is a one time process in the transfer lifecycle. Since you’d be sending money to multiple recipients at interval, you’d only be fetching the recipients from your DB for each transfer request.

Managing batches

Before creating the transfer request, you need to break the requests into batches. A batch is a subset of your requests that makes it easier to manage and track your transfers. In code, a batch is an array of transfer objects:

1{
2 "transfers": [
3 {
4 "amount": 20000,
5 "reference": "4b36bee6-2d1d-41b6-ab1e-6465c04e6765",
6 "reason": "Why not?",
7 "recipient": "RCP_2tn9clt23s7qr28"
8 },
9 {
10 "amount": 30000,
11 "reference": "79c79c1f-a4fb-446f-a9b6-e743a6bdd61d",
12 "reason": "Because I can",
13 "recipient": "RCP_1a25w1h3n0xctjg"
14 },
15 {
16 "amount": 40000,
17 "reference": "d5d2203b-40db-418f-845e-78786f291c38",
18 "reason": "Coming right up",
19 "recipient": "RCP_aps2aibr69caua7"
20 }
21 ]
22}
Generate transfer reference

You should always generate a unique transfer reference for each transfer object. The transfer reference will help you keep track and manage each transfer. You can check out the generating a transfer reference section to learn more.

Each object in the transfers array is the same parameters for a single request. A batch should not contain more than 100 items and each batch should be sent every 5 seconds:

ParameterConfig
Batch size<= 100
DurationEvery 5 seconds

Merchants who have set up transfer approval via URL should ensure they can approve all transfers in each batch within a few seconds (ideally, microseconds), else they risk the possibility of ending up with transfers with blocked status. So while planning the batch size, you should factor in the time it takes to verify the authenticity of each transfer.

The duration is to avoid getting rate limited. Sending multiple requests at short intervals would lead to a 429 (Too many requests) error.

With your batch properly planned and implemented, you can now initiate the bulk transfer.

Initiate bulk transfer

Disable OTP

If you’ve enabled OTP for transfer approval, you need to disable it to use this endpoint.

In addition to the array of transfers in your batch, you need to add the currency and source to make a request to the Bulk TransferAPI endpoint:

Show Response
1#!/bin/sh
2url="https://api.paystack.co/transfer/bulk"
3authorization="Authorization: Bearer YOUR_SECRET_KEY"
4content_type="Content-Type: application/json"
5data='{
6 "currency": "NGN",
7 "source": "balance",
8 "transfers": [
9 {
10 "amount": 20000,
11 "reference": "588YtfftReF355894J",
12 "reason": "Why not?",
13 "recipient": "RCP_2tn9clt23s7qr28"
14 },
15 {
16 "amount": 30000,
17 "reference": "YunoTReF35e0r4J",
18 "reason": "Because I can",
19 "recipient": "RCP_1a25w1h3n0xctjg"
20 },
21 {
22 "amount": 40000,
23 "reason": "Coming right up",
24 "recipient": "RCP_aps2aibr69caua7"
25 }
26 ]
27}'
28
29curl "$url" -H "$authorization" -H "$content_type" -d "$data" -X POST
1{
2 "status": true,
3 "message": "3 transfers queued.",
4 "data": [
5 {
6 "reference": "588YtfftReF355894J",
7 "recipient": "RCP_2tn9clt23s7qr28",
8 "amount": 20000,
9 "transfer_code": "TRF_ful4rvpbiuaph4fo",
10 "currency": "NGN",
11 "status": "received"
12 },
13 {
14 "reference": "YunoTReF35e0r4J",
15 "recipient": "RCP_1a25w1h3n0xctjg",
16 "amount": 30000,
17 "transfer_code": "TRF_0lztrf3rox1rpbw1",
18 "currency": "NGN",
19 "status": "received"
20 },
21 {
22 "reference": "nm7kjk9gb-l5i4lr9wq3",
23 "recipient": "RCP_aps2aibr69caua7",
24 "amount": 40000,
25 "transfer_code": "TRF_hsk59k6loek7vlut",
26 "currency": "NGN",
27 "status": "received"
28 }
29 ]
30}

Unlike single transfers, the data parameter in the response object returns an array of objects. Each object represent a transfer in your batch. It’s important to note that the transfer status works like the single transfer. You can take a look at the transfer lifecycle if you need a refresher on how a transfer status is updated.

Test transfers always return success, because there is no processing involved. Live transfers are queued while they are being processed so you need to set up a webhook URL to receive the updates on your request.

Heads up!

While you might have sent your transfers in batches, notifications are sent for each transfer in a batch. So if you have 100 transfers in a batch, you’ll receive 100 events (one per transfer) for that batch.

Verify a transfer

The update on a transfer isn’t always instant because of the queuing and processing time, so you need to set up a webhook URL where we’ll POST updates to when processing is completed.

Receiving Notifications

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

Once a transfer is processed, we send 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": {
45 "provider": null,
46 "id": null
47 },
48 "created_at": "2020-10-26T12:28:57.000Z",
49 "updated_at": "2020-10-26T12:28:57.000Z"
50 }
51}

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 get the status and details of the transfer.