Paystack OTP for transfers is a security measure that sends a one-time code to the business owner for confirmation before a transfer processes. To disable it for automation, call POST /transfer/disable_otp, receive a final OTP, then call POST /transfer/disable_otp_finalize with that code. After that, all transfers process without manual confirmation. Re-enable anytime with POST /transfer/enable_otp.
When OTP is enabled on your Paystack account and you initiate a transfer, Paystack does not process it immediately. Instead, the transfer enters an otp status, and Paystack sends a one-time password to the phone number or email registered on your Paystack business account. You must submit that OTP to finalize the transfer.
// Step 1: Initiate transfer (OTP enabled)
var transferResponse = await fetch('https://api.paystack.co/transfer', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source: 'balance',
amount: 500000,
recipient: 'RCP_abc123',
reason: 'Vendor payout',
reference: 'payout_001',
}),
});
var data = await transferResponse.json();
// data.data.status => "otp"
// data.data.transfer_code => "TRF_xyz789"
// Step 2: Submit OTP (received via SMS or email)
var finalizeResponse = await fetch('https://api.paystack.co/transfer/finalize_transfer', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
transfer_code: data.data.transfer_code,
otp: '928374', // The code from SMS/email
}),
});
This is a deliberate friction point. Paystack wants to make sure that the person initiating the transfer is authorized to move money out of the account. Even if someone gets your API secret key, they still need access to the registered phone or email to complete a transfer.
The OTP is valid for a limited time. If you do not submit it, the transfer eventually expires and the reserved funds return to your available balance.
OTP works well for:
OTP does not work for:
If you are building anything that needs to send money without a human typing in a code, you need to disable OTP. But before you do that, understand what you are giving up and what you need to build in its place.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Disabling OTP is a two-step process. Paystack sends you one final OTP to confirm you actually want to disable the feature. This prevents someone who stole your API key from silently disabling OTP and then draining your account.
// Step 1: Request OTP disabling
var disableResponse = await fetch('https://api.paystack.co/transfer/disable_otp', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
});
var disableData = await disableResponse.json();
console.log(disableData.message);
// "OTP has been sent to your phone/email. Call disable_otp_finalize to complete."
// Step 2: Finalize with the OTP you received
var finalizeResponse = await fetch('https://api.paystack.co/transfer/disable_otp_finalize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
otp: '482916', // The code from SMS/email
}),
});
var finalizeData = await finalizeResponse.json();
console.log(finalizeData.message);
// "OTP requirement for transfers has been disabled"
After this, all future transfers skip the OTP step. When you initiate a transfer, the status goes straight to pending instead of otp. Processing starts immediately.
You can also disable OTP through the Paystack dashboard under Settings > Transfers. The dashboard flow is identical: you click disable, receive an OTP, enter it, and OTP is off.
If you need to turn OTP back on (security incident, change in process, testing), one API call does it:
var enableResponse = await fetch('https://api.paystack.co/transfer/enable_otp', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
});
var enableData = await enableResponse.json();
console.log(enableData.message);
// "OTP requirement for transfers has been enabled"
This takes effect immediately. The next transfer you initiate will require OTP. Any transfers already in the pending state continue processing normally. Only new transfers are affected.
Consider building a "panic button" in your admin dashboard that re-enables OTP with one click. If you detect suspicious activity or a potential key compromise, you want to be able to lock down transfers in seconds, not minutes.
With OTP enabled, stealing your API key is not enough to steal your money. The attacker also needs access to your phone or email. With OTP disabled, the API key is the only thing standing between an attacker and your balance. That is the trade-off in plain terms.
What you lose:
What you must implement to compensate:
The goal is to replace OTP (a Paystack-level control) with your own controls that are better suited to automated systems. The most common pattern is maker-checker.
// Maker-checker payout approval flow
// Step 1: Maker creates payout requests
async function createPayoutRequest(makerId, recipientCode, amount, reason) {
var payout = await db.payouts.create({
makerId: makerId,
recipientCode: recipientCode,
amount: amount,
reason: reason,
status: 'pending_approval',
createdAt: new Date(),
});
return payout;
}
// Step 2: Checker approves (must be a different person)
async function approvePayout(payoutId, checkerId) {
var payout = await db.payouts.findById(payoutId);
if (payout.makerId === checkerId) {
throw new Error('Checker cannot be the same person as maker');
}
if (payout.status !== 'pending_approval') {
throw new Error('Payout is not awaiting approval');
}
// Check daily limits
var todayTotal = await db.payouts.sumApprovedToday(checkerId);
if (todayTotal + payout.amount > 5000000) { // 50,000 NGN daily limit
throw new Error('Daily approval limit exceeded');
}
await db.payouts.update(payoutId, {
status: 'approved',
checkerId: checkerId,
approvedAt: new Date(),
});
}
// Step 3: Automated system picks up approved payouts and sends them
async function processApprovedPayouts() {
var approved = await db.payouts.findByStatus('approved');
for (var i = 0; i < approved.length; i++) {
await processPayout(approved[i].id);
}
}
This gives you more control than OTP ever did. You can enforce that the person who creates the payout is different from the person who approves it. You can set daily limits per approver. You can require multiple approvers for high-value transfers. And the entire approval trail is in your database for auditing.
For a complete approval workflow design, see payout approval workflows and maker-checker controls.
Test OTP enable/disable behavior in Paystack's test mode before touching your live account. In test mode, transfers do not move real money, so you can experiment without risk.
// Use your test secret key for OTP experiments
// PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 1. Check current OTP status by initiating a test transfer
var testTransfer = await fetch('https://api.paystack.co/transfer', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_TEST_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source: 'balance',
amount: 10000,
recipient: 'RCP_test_recipient',
reference: 'otp_test_' + Date.now(),
}),
});
var result = await testTransfer.json();
console.log('Transfer status: ' + result.data.status);
// "otp" means OTP is enabled
// "pending" means OTP is disabled
After verifying the behavior in test mode, follow the same steps on your live account. Keep your live OTP changes to a planned window. Communicate with your team before disabling OTP on production, and make sure your application-level controls are deployed and tested before you flip the switch.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course