Send money locally with Klasha to bank accounts and mobile money wallets in Africa.


  • NGN
  • ZAR
  • GHS (beta)
  • KES (beta)
You can transfer money in four easy steps:
  1. 1.
    Verify the account number
  2. 2.
    Create transfer recipient
  3. 3.
    Initiate a transfer
  4. 4.
    Listen for status.

Before you begin!

  • Link to the Postman collection
  • Find your keys on the Klasha Dashboard → Settings → Generate API Keys (here)
  • Klasha Dashboard, Generate API Keys page
Generate bearer token
Using the Token endpoint on the Postman collection, the token can be obtained from the headers under the header name: token. See the screenshot below for an example:
Fetch bank codes
From the bank code API on the Postman collection here, you can fetch the bank codes. see the screenshot below for an example
Verify account number
Using the Resolve account number endpoint on the Postman collection here, verify the account number by making use of the bank codes gotten from here. See an example in the screenshot below

Generate a transfer reference

It’s a unique reference sent as requestId that can be used to uniquely identify a transfer.

Initiate a transfer

Create payload
"amount": 1000,
"country": "NG",
"currency": "NGN",
"bankCode": "044",
"bankName": "Access Bank",
"accountNumber": "0690000032",
"accountName": "Pastor Bright",
"requestId": "kbtr-3857-011-null-166993253334816",
"description": "certification"
Fields specification:
- amount is the amount to payout
- country is the country of the beneficiary
- currency is the currency of payout
- bankcode is the code of the bank which can be gotten by calling the Bank code endpoint
- bankName is the name of the bank
- accountNumber is the beneficiary account number
- accountName is the beneficiary account name
- requestId is the transaction reference
- description is the narration or reason for the payout
  • Serialize the payload
Encryption Algorithm
You need to encrypt the entire create transfer payload that we specify on the previous point (here). In order to encrypt the body correctly, you’d need the encryptionKey (that you can obtain following this guide here). We also provided an example in Java of the encryption algorithm in order to help you.
public static String encryptCBC(String stringToEncrypt, String encryptionkey) {
try {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[8];
final byte[][] keyAndIV =GenerateKeyAndIV(32, 16, 1, salt, password.getBytes(StandardCharsets.UTF_8),
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIV[0], "AES"), new IvParameterSpec(keyAndIV[1]));
byte[] encryptedData = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
byte[] prefixAndSaltAndEncryptedData = new byte[16 + encryptedData.length];
// Copy prefix (0-th to 7-th bytes)
System.arraycopy("Salted__".getBytes(StandardCharsets.UTF_8), 0, prefixAndSaltAndEncryptedData, 0, 8);
// Copy salt (8-th to 15-th bytes)
System.arraycopy(salt, 0, prefixAndSaltAndEncryptedData, 8, 8);
// Copy encrypted data (16-th byte and onwards)
System.arraycopy(encryptedData, 0, prefixAndSaltAndEncryptedData, 16, encryptedData.length);
return Base64.getEncoder().encodeToString(prefixAndSaltAndEncryptedData);
} catch (Exception e) {
throw new RuntimeException(e);
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
generatedLength += digestLength;
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte) 0);
Encrypted Payload
The result of the encryption of the payload that we specified here have to be used with the Merchant payout endpoint as shown below:

Listen for a status response

When a transfer is initiated, it could take a few seconds or minutes to be processed. This is why we recommend relying on webhooks for verification as opposed to polling.
Once a transfer is processed, we send the final status of the transfer as a POST request to your webhook URL.
"data": {
"reference": "kbtr-3857-011-null-166993253334816",
"createdAt": "2023-03-28T23:01:45.336",
"amount": 1000,
"accountName": "Pastor Bright",
"narration": "certification",
"name": "Steph and sons",
"currency": "NGN",
"accountNumber": "0690000032",
"status": "failed"
"event": "payout"
"data": {
"reference": "kbtr-3857-011-null-166993253331236",
"createdAt": "2023-03-28T23:01:45.336",
"amount": 1000,
"accountName": "Pastor Bright",
"narration": "certification",
"name": "Steph and sons",
"currency": "NGN",
"accountNumber": "0690000032",
"status": "successful"
"event": "payout"
The possible statuses are listed and described in the following table:
This is sent when the transfer is successful
This is sent when the transfer fails
This transfer is still in progress. Please wait for either success or failed.

Polling transfer statuses

  • You can fetch the transfer from the Find by ref API on the Postman collection to know the status. See example in the below screenshot.

Source wallet

All transfers would be debited from NGN Wallet balance. Ensure you have sufficient balance before making any transfer request.