A Node.js wrapper for the Monnify API, giving you clean, validated access to every Monnify service — collections, disbursements, invoices, direct debit, bills payment, and more — without having to hand-craft HTTP requests yourself.
- Requirements
- Installation
- Setup & Credentials
- How Authentication Works
- Modules at a Glance
- Reserved Accounts
- Transactions
- Sub Accounts
- Invoices
- Settlements
- Disbursements
- Refunds
- Wallet
- Limit Profiles
- Direct Debit
- Bills Payment
- Verification (Value-Added Services)
- Error Handling
- Running the Tests
- Contributing
- Node.js 18 or higher
- A Monnify account — get your API key and secret from the Monnify dashboard
npm install monnify-nodejs-libYou need three things from your Monnify dashboard:
| Variable | Where to find it |
|---|---|
MONNIFY_ENV |
SANDBOX for testing, LIVE for production |
MONNIFY_APIKEY |
Dashboard → Settings → API Keys |
MONNIFY_SECRET |
Dashboard → Settings → API Keys |
CONTRACT |
Dashboard → Settings → Contract Code |
The recommended approach is a .env file so credentials never appear in source code:
# .env
MONNIFY_ENV=SANDBOX
MONNIFY_APIKEY=MK_TEST_XXXXXXXXXX
MONNIFY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
CONTRACT=1234567890
WALLETACCOUNTNUMBER=0123456789 # only needed for disbursement wallet balanceThe library reads
MONNIFY_ENVautomatically — no need to pass it to any constructor. It will also detect if your API key prefix does not match your declared environment (e.g. aMK_PROD_key withMONNIFY_ENV=SANDBOX) and throw a clear error.
Then load it at the top of your app:
import dotenv from 'dotenv';
dotenv.config();
⚠️ Add.envto your.gitignore. Never commit credentials to source control.
Every Monnify API call needs a bearer token. The library handles token fetching and caching automatically — you just call getToken() once and pass the result to each method.
import { Transaction } from 'monnify-nodejs-lib';
const api = new Transaction();
const [statusCode, token] = await api.getToken();
// token is the raw bearer string — pass it to every method call belowThe token is cached in memory and reused until it expires, so calling getToken() multiple times is cheap.
The environment is read from
MONNIFY_ENVin your environment variables. All instances in the same process must use the same environment.
import {
MonnifyAPI, // single entry point — all modules in one object
Transaction, // individual import — useful for tree-shaking
ReservedAccount,
SubAccount,
Invoice,
Settlement,
Disbursement,
TransactionRefund,
Wallet,
LimitProfile,
DirectDebit,
Verification,
BillsPayment
} from 'monnify-nodejs-lib';Using the unified MonnifyAPI class:
import { MonnifyAPI } from 'monnify-nodejs-lib';
const monnify = new MonnifyAPI({
MONNIFY_APIKEY: process.env.MONNIFY_APIKEY,
MONNIFY_SECRET: process.env.MONNIFY_SECRET,
});
const [, token] = await monnify.getToken();
// Access every module through the same instance
await monnify.transaction.initTransaction(token, payload);
await monnify.reservedAccount.createReservedAccount(token, payload);
await monnify.disbursement.initiateSingleTransfer(token, payload);
// ...and so onA reserved account is a dedicated virtual bank account assigned to a specific customer. Any transfer made to it is automatically matched to that customer.
import { ReservedAccount } from 'monnify-nodejs-lib';
const api = new ReservedAccount();
const [, token] = await api.getToken();| Method | Description |
|---|---|
createReservedAccount(token, data) |
Create a permanent virtual account for a customer |
createInvoiceReservedAccount(token, data) |
Create a one-time virtual account tied to an invoice |
addLinkedAccounts(token, data) |
Add preferred banks to an existing reserved account |
reservedAccountDetails(token, data) |
Retrieve details of a reserved account |
reservedAccountTransactions(token, data) |
List transactions received on a reserved account |
updateReservedAccountKycInfo(token, data) |
Update BVN / NIN on a reserved account |
updateReservedAccountBvn(token, data) |
Update just the BVN |
updatePaymentSources(token, data) |
Restrict which payment sources are accepted |
updateIncomeSplitConfig(token, data) |
Update how incoming funds are split across sub-accounts |
deallocateReservedAccount(token, data) |
Permanently delete a reserved account |
import crypto from 'crypto';
const [statusCode, response] = await api.createReservedAccount(token, {
customerName: 'Ada Obi',
customerEmail: 'ada@example.com',
accountName: 'Ada Obi',
accountReference: crypto.randomBytes(16).toString('hex'), // your unique ref
contractCode: process.env.CONTRACT,
bvn: '22222222222',
currencyCode: 'NGN',
paymentMethods: ['ACCOUNT_TRANSFER']
});
console.log(response.responseBody.accountNumber); // the virtual account numberManage the full payment lifecycle — from initialising a checkout to charging a card or generating a bank transfer.
import { Transaction } from 'monnify-nodejs-lib';
const api = new Transaction();
const [, token] = await api.getToken();| Method | Description |
|---|---|
initTransaction(token, data) |
Start a new payment — returns a transactionReference |
getTransactionStatusv2(token, data) |
Check status by transactionReference (recommended) |
getTransactionStatusv1(token, data) |
Check status by paymentReference (your own reference) |
getAllTransactions(token, filters?) |
Search / list transactions with optional filters |
payWithBankTransfer(token, data) |
Generate a one-time bank account for an existing transaction |
payWithUssd(token, data) |
Generate a USSD string for an existing transaction |
chargeCard(token, data) |
Charge a card directly (requires transactionReference) |
authorizeOtp(token, data) |
Submit the OTP for a card charge that requires one |
ThreeDsSecureAuthTransaction(token, data) |
Complete a 3DS card authentication |
cardTokenization(token, data) |
Charge a previously saved card token |
from/todate filters — pass Unix timestamps in milliseconds (not ISO strings).const to = Date.now(); const from = to - 7 * 24 * 60 * 60 * 1000; // 7 days ago const [, resp] = await api.getAllTransactions(token, { from, to, page: 0, size: 20 });
import crypto from 'crypto';
// 1. Initialise the transaction
const [, initResp] = await api.initTransaction(token, {
customerName: 'Emeka Dike',
customerEmail: 'emeka@example.com',
amount: 5000, // in Naira
paymentDescription: 'Order #1042',
paymentReference: crypto.randomBytes(16).toString('hex'),
contractCode: process.env.CONTRACT,
currencyCode: 'NGN',
paymentMethods: ['CARD', 'ACCOUNT_TRANSFER'],
redirectUrl: 'https://yourapp.com/payment/callback'
});
const { transactionReference, paymentReference, checkoutUrl } = initResp.responseBody;
// Redirect your customer to checkoutUrl, or continue with one of the methods below
// 2a. Pay via bank transfer
const [, transferResp] = await api.payWithBankTransfer(token, {
transactionReference,
bankCode: '058' // optional — generates USSD string for this bank
});
// 2b. OR charge a card directly
const [, cardResp] = await api.chargeCard(token, {
transactionReference,
card: {
number: '4111111111111111',
expiryMonth: '10',
expiryYear: '2025',
pin: '1234',
cvv: '123'
},
deviceInformation: {
httpBrowserLanguage: 'en-US',
httpBrowserJavaEnabled: false,
httpBrowserJavaScriptEnabled: true,
httpBrowserColorDepth: 24,
httpBrowserScreenHeight: 1203,
httpBrowserScreenWidth: 2138,
httpBrowserTimeDifference: '',
userAgentBrowserValue: 'Mozilla/5.0 ...'
}
});
// 3. Check the outcome
const [, statusResp] = await api.getTransactionStatusv2(token, { transactionReference });
console.log(statusResp.responseBody.paymentStatus); // 'PAID', 'PENDING', etc.After a successful first charge, Monnify returns a cardToken in the transaction status response. Save it alongside the customer email and use it for all future charges — no card details needed again.
// After a successful chargeCard call, query the status:
const [, statusResp] = await api.getTransactionStatusv2(token, { transactionReference });
const cardToken = statusResp.responseBody.cardToken; // e.g. "MNFY_0CD0138B..."
const customerEmail = 'emeka@example.com'; // same email used in initTransaction
// Save cardToken + customerEmail in your database, then for future charges:
const [, recurringResp] = await api.cardTokenization(token, {
cardToken,
customerEmail,
customerName: 'Emeka Dike',
amount: 5000,
paymentReference: crypto.randomBytes(16).toString('hex'),
paymentDescription: 'Monthly subscription',
contractCode: process.env.CONTRACT,
currencyCode: 'NGN',
apiKey: process.env.MONNIFY_APIKEY
});Split incoming payments automatically across multiple bank accounts.
import { SubAccount } from 'monnify-nodejs-lib';
const api = new SubAccount();
const [, token] = await api.getToken();| Method | Description |
|---|---|
createSubAccount(token, data) |
Create a new sub-account |
getSubAccounts(token) |
List all sub-accounts |
updateSubAccount(token, data) |
Update a sub-account |
deleteSubAccount(token, data) |
Remove a sub-account |
const [statusCode, response] = await api.createSubAccount(token, [{
currencyCode: 'NGN',
bankCode: '058',
accountNumber: '2085086393',
email: 'partner@example.com',
defaultSplitPercentage: 20 // this account gets 20% of every payment
}]);Create payment invoices with an expiry date and track their status.
import { Invoice } from 'monnify-nodejs-lib';
const api = new Invoice();
const [, token] = await api.getToken();| Method | Description |
|---|---|
createInvoice(token, data) |
Create a new invoice |
viewInvoiceDetails(token, data) |
Get details of a specific invoice |
getAllInvoices(token, data?) |
List all invoices with pagination |
cancelInvoice(token, data) |
Cancel an unpaid invoice |
import crypto from 'crypto';
const [statusCode, response] = await api.createInvoice(token, {
amount: 10000,
invoiceReference: crypto.randomBytes(16).toString('hex'),
description: 'Consulting fee - May 2026',
contractCode: process.env.CONTRACT,
customerName: 'Bola Tinubu Ltd',
customerEmail: 'accounts@bolaLtd.com',
paymentMethods: ['CARD', 'ACCOUNT_TRANSFER'],
currencyCode: 'NGN'
});
console.log(response.responseBody.invoiceLink); // share this link with your customerQuery how funds from transactions have been settled to your bank account.
import { Settlement } from 'monnify-nodejs-lib';
const api = new Settlement();
const [, token] = await api.getToken();| Method | Description |
|---|---|
getTransactionsBySettlementReference(token, data) |
List all transactions in a settlement batch |
getSettlementInfo(token, data) |
Get settlement details for a specific transaction |
// Find all transactions in a settlement batch
const [, response] = await api.getTransactionsBySettlementReference(token, {
reference: 'MSP_20260101_001'
});
// Or look up the settlement for one transaction
const [, info] = await api.getSettlementInfo(token, {
transactionReference: 'MNFY|24|20260115120000|000001'
});Send money out of your Monnify wallet — single transfers, bulk transfers, and everything in between.
import { Disbursement } from 'monnify-nodejs-lib';
const api = new Disbursement();
const [, token] = await api.getToken();| Method | Description |
|---|---|
initiateSingleTransfer(token, data) |
Send money to one bank account |
initiateBulkTransfer(token, data) |
Send money to many accounts in one request |
authorizeSingleTransfer(token, data) |
Authorise a single transfer (OTP step) |
authorizeBulkTransfer(token, data) |
Authorise a bulk transfer (OTP step) |
resendTransferOTP(token, data) |
Resend the OTP for a single transfer |
resendBulkTransferOTP(token, data) |
Resend the OTP for a bulk transfer |
getSingleTransferStatus(token, data) |
Check the status of a single transfer |
getBulkTransferStatus(token, data) |
Check the summary of a bulk batch |
getBulkBatchSummary(token, data) |
Alias for getBulkTransferStatus |
getBulkTransferTransactions(token, data) |
List individual transactions within a bulk batch |
getAllSingleTransfers(token, data?) |
Paginated list of all single transfers |
getAllBulkTransfers(token, data?) |
Paginated list of all bulk transfers |
searchDisbursementTransactions(token, data) |
Search disbursement transactions with filters |
import crypto from 'crypto';
const [statusCode, response] = await api.initiateSingleTransfer(token, {
sourceAccountNumber: process.env.WALLETACCOUNTNUMBER,
destinationBankCode: '058',
destinationAccountNumber: '2085086393',
destinationAccountName: 'John Doe',
amount: 5000,
currencyCode: 'NGN',
narration: 'Freelancer payment - May 2026',
reference: crypto.randomBytes(16).toString('hex')
});Reverse a payment back to the customer's original payment method.
import { TransactionRefund } from 'monnify-nodejs-lib';
const api = new TransactionRefund();
const [, token] = await api.getToken();| Method | Description |
|---|---|
initiateRefund(token, data) |
Start a refund for a completed transaction |
getAllRefunds(token, data?) |
List all refunds |
getRefundStatus(token, data) |
Check the status of a specific refund |
import crypto from 'crypto';
const [statusCode, response] = await api.initiateRefund(token, {
transactionReference: 'MNFY|24|20260115120000|000001',
refundReference: crypto.randomBytes(16).toString('hex'),
refundReason: 'Customer requested cancellation',
refundAmount: 5000,
customerNote: 'Your refund is on the way',
destinationAccountNumber: '2085086393',
destinationAccountBankCode: '058'
});Check the available balance in your Monnify disbursement wallet.
import { Wallet } from 'monnify-nodejs-lib';
const api = new Wallet();
const [, token] = await api.getToken();| Method | Description |
|---|---|
getWalletBalance(token, data) |
Get available and ledger balance for a wallet account |
const [statusCode, response] = await api.getWalletBalance(token, {
accountNumber: process.env.WALLETACCOUNTNUMBER
});
const { availableBalance, ledgerBalance } = response.responseBody;
console.log(`Available: ₦${availableBalance}`);Create and manage transaction limit profiles, then attach them to reserved accounts for spending controls.
import { LimitProfile } from 'monnify-nodejs-lib';
const api = new LimitProfile();
const [, token] = await api.getToken();| Method | Description |
|---|---|
createLimitProfile(token, data) |
Create a new limit profile |
getLimitProfiles(token) |
List all limit profiles on your account |
updateLimitProfile(token, data) |
Update an existing limit profile |
reserveAccountWithLimit(token, data) |
Create a reserved account with a limit profile attached |
updateReserveAccountLimit(token, data) |
Change the limit profile on an existing reserved account |
// Create a profile that caps daily spending at ₦500,000
const [, profileResp] = await api.createLimitProfile(token, {
limitProfileName: 'Retail Customer Limit',
singleTransactionValue: 100000, // max per transaction: ₦100,000
dailyTransactionVolume: 10, // max 10 transactions per day
dailyTransactionValue: 500000 // max ₦500,000 per day
});
const limitProfileCode = profileResp.responseBody.limitProfileCode;Set up recurring debit mandates — charge a customer's bank account on a schedule without them needing to be present for each payment.
import { DirectDebit } from 'monnify-nodejs-lib';
const api = new DirectDebit();
const [, token] = await api.getToken();Note: Direct Debit requires regulatory approval from Monnify. Contact your account manager to get it enabled.
| Method | Description |
|---|---|
createMandate(token, data) |
Create a new debit mandate |
getMandateStatus(token, data) |
Check the status of a mandate |
debitMandate(token, data) |
Trigger a debit against an active mandate |
getDebitStatus(token, data) |
Check the outcome of a debit attempt |
cancelMandate(token, data) |
Cancel an active mandate |
import crypto from 'crypto';
// 1. Create the mandate
const [, mandateResp] = await api.createMandate(token, {
contractCode: process.env.CONTRACT,
mandateReference: crypto.randomBytes(12).toString('hex'),
mandateDescription: 'Monthly gym subscription',
mandateStartDate: '2026-01-01T00:00:00',
mandateEndDate: '2026-12-31T23:59:59',
customerName: 'Ngozi Eze',
customerEmailAddress: 'ngozi@example.com',
customerPhoneNumber: '08011223344',
customerAddress: '12 Adeola Odeku, Victoria Island, Lagos',
customerAccountNumber: '2085086393',
customerAccountBankCode: '058',
mandateAmount: 120000, // total lifetime cap
autoRenew: false,
customerCancellation: true,
redirectUrl: 'https://yourapp.com/direct-debit/callback'
});
// 2. Once approved, debit the mandate
const [, debitResp] = await api.debitMandate(token, {
mandateCode: mandateResp.responseBody.mandateCode,
debitAmount: 10000,
paymentReference: crypto.randomBytes(12).toString('hex'),
narration: 'May 2026 subscription',
customerEmail: 'ngozi@example.com'
});Pay utility bills, buy airtime, subscribe to cable TV, and more. The flow always goes in the same order:
getBillerCategories() → pick a category (e.g. "CABLE_TV")
└─ listBillers({ categoryCode }) → pick a biller (e.g. "DSTV")
└─ getBillerProducts({ billerCode }) → pick a product / plan
└─ validateCustomer({ productCode, customerId }) → validate the customer
└─ vendBill({ productCode, customerId, amount, reference })
└─ requeryBillPayment({ reference }) → confirm outcome if needed
import { BillsPayment } from 'monnify-nodejs-lib';
const api = new BillsPayment();
const [, token] = await api.getToken();| Method | Description |
|---|---|
getBillerCategories(token, data?) |
List all biller categories (Airtime, Cable TV, Electricity, etc.) |
listBillers(token, data?) |
List billers, optionally filtered by categoryCode |
getBillerProducts(token, data) |
List products / plans for a specific biller |
validateCustomer(token, data) |
Validate a customer's identifier before paying |
vendBill(token, data) |
Actually pay the bill |
requeryBillPayment(token, data) |
Re-check the outcome of a bill payment |
import crypto from 'crypto';
// 1. Find categories
const [, categoriesResp] = await api.getBillerCategories(token);
// e.g. categories: [{ categoryCode: 'CABLE_TV', categoryName: 'Cable TV' }, ...]
// 2. Find billers in a category
const [, billersResp] = await api.listBillers(token, { categoryCode: 'CABLE_TV' });
const { billerCode } = billersResp.responseBody.content[0];
// 3. Find products for the biller
const [, productsResp] = await api.getBillerProducts(token, { billerCode });
const { productCode, amount } = productsResp.responseBody.content[0];
// 4. Validate the customer's smart card number
const [, validateResp] = await api.validateCustomer(token, {
productCode,
customerId: '1234567890' // e.g. DSTV smart card number
});
const validationReference = validateResp.responseBody?.vendInstruction?.requireValidationRef
? validateResp.responseBody.vendInstruction.validationReference
: undefined;
// 5. Pay the bill
const reference = crypto.randomBytes(16).toString('hex');
const [statusCode, vendResp] = await api.vendBill(token, {
productCode,
customerId: '1234567890',
amount,
reference,
validationReference, // include only if step 4 said it was required
emailAddress: 'customer@example.com'
});
// 6. If the outcome was unclear, requery
if (statusCode !== 200) {
const [, requery] = await api.requeryBillPayment(token, { reference });
console.log(requery.responseBody);
}Validate bank accounts and verify customer identity documents.
import { Verification } from 'monnify-nodejs-lib';
const api = new Verification();
const [, token] = await api.getToken();| Method | Description |
|---|---|
validateBankAccount(token, data) |
Confirm that an account number belongs to the expected name |
verifyBvnInformation(token, data) |
Retrieve BVN holder details |
matchBvnAndAccountName(token, data) |
Check that a BVN and bank account belong to the same person |
verifyNin(token, data) |
Retrieve NIN holder details |
// Verify a bank account before sending money
const [statusCode, response] = await api.validateBankAccount(token, {
accountNumber: '2085086393',
bankCode: '058'
});
console.log(response.responseBody.accountName); // the registered account name
// Verify NIN
const [, ninResp] = await api.verifyNin(token, { nin: '12345678901' });Every method returns a two-element array: [httpStatusCode, responseBody]. Check the status code before reading the body.
const [statusCode, response] = await api.initiateSingleTransfer(token, payload);
if (statusCode === 200) {
console.log('Transfer successful:', response.responseBody);
} else {
console.error(`Transfer failed (${statusCode}):`, response);
}If you pass an invalid or incomplete payload, the library throws a validation error before making any network request:
try {
await api.createMandate(token, { mandateReference: 'REF001' }); // missing required fields
} catch (err) {
console.error(err.message); // "mandateDescription" is required ...
}Clone the repo and install dependencies:
git clone https://github.com/Monnify/Monnify-Nodejs-lib.git
cd Monnify-Nodejs-lib
npm installExport your sandbox credentials, then run the full test suite:
export MONNIFY_APIKEY=MK_TEST_XXXXXXXXXX
export MONNIFY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export CONTRACT=1234567890
export WALLETACCOUNTNUMBER=0123456789
npm testThe suite runs 71 tests across all 11 modules. All tests target the Monnify sandbox — no real money moves.
Contributions are welcome. Please open a pull request with a clear description of your changes. All PRs must:
- Pass
npm testwith no failures - Follow the existing pattern: class extends
BaseRequestAPI, Joi validators in/validators/, tests in/tests/ - Target the
devbranch, notmain