Paystack requires an OTP for every transfer by default. Your API call is not failing. It is working as designed. To automate transfers without manual OTP entry, call the POST /transfer/disable_otp endpoint, receive a confirmation OTP on your registered email or phone, then call POST /transfer/finalize_disable_otp with that OTP. After this, transfers will go through without requiring an OTP. This is a one-time setup per Paystack account.
You call the Paystack Transfer API to send money to a bank account. Instead of processing the transfer, Paystack returns this:
{
"status": true,
"message": "OTP is required to complete this transfer",
"data": {
"integration": 123456,
"domain": "live",
"transfer_code": "TRF_abc123def456",
"status": "otp",
"reference": "your-reference-123"
}
}
Notice that status is true. This is not an HTTP error. Paystack accepted your transfer request but put it on hold until you provide an OTP. The transfer sits in an "otp" state and will not proceed until you either submit the OTP or disable OTP for your account entirely.
If you are building an automated system (payroll, vendor payments, wallet withdrawals), this blocks everything. You cannot manually enter an OTP for every transfer at 2 AM when your cron job runs.
OTP stands for one-time password. When OTP is enabled on your Paystack account, every transfer triggers a code sent to the account owner's registered phone number or email. You must submit that code to finalize the transfer.
Paystack enables OTP by default for a good reason: transfers move money out of your Paystack balance to external bank accounts. If someone gains access to your secret key, they could drain your entire balance by initiating transfers to their own accounts. OTP is the last line of defense against this.
Think of it this way:
OTP protects the dangerous direction: money going out. That is why Paystack defaults to requiring it.
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. First, you request to disable it. Paystack sends a confirmation OTP to your registered email or phone. Then you submit that OTP to finalize the disabling. After that, all future transfers process without OTP.
Step 1: Request to disable OTP
// Step 1: Call disable_otp
async function requestDisableOtp() {
const response = await fetch('https://api.paystack.co/transfer/disable_otp', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log(data);
// { status: true, message: "OTP has been sent to ... to confirm disabling of OTP ..." }
return data;
}
After this call, check the email or phone number associated with your Paystack account. You will receive a code.
Step 2: Finalize with the OTP you received
// Step 2: Submit the OTP to finalize
async function finalizeDisableOtp(otp) {
const response = await fetch('https://api.paystack.co/transfer/finalize_disable_otp', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ otp }),
});
const data = await response.json();
console.log(data);
// { status: true, message: "OTP requirement for transfers has been disabled" }
return data;
}
Once Step 2 succeeds, all future transfers on this Paystack account will process without requiring an OTP. You do not need to repeat this process unless you re-enable OTP.
With OTP disabled, your transfer code looks like this:
async function initiateTransfer(recipientCode, amount, reference, reason) {
const response = 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: amount * 100, // Convert to kobo/pesewas
recipient: recipientCode,
reference,
reason,
}),
});
const data = await response.json();
if (data.status && data.data.status === 'success') {
console.log('Transfer completed:', data.data.transfer_code);
return { success: true, data: data.data };
}
if (data.data?.status === 'otp') {
// OTP is still required. The disable did not take effect.
console.error('OTP still required. Run disable_otp and finalize_disable_otp again.');
return { success: false, reason: 'otp_required' };
}
if (data.data?.status === 'pending') {
// Transfer is queued and will be processed shortly
console.log('Transfer pending:', data.data.transfer_code);
return { success: true, pending: true, data: data.data };
}
return { success: false, message: data.message };
}
After disabling OTP, the transfer response will have a status of "success" (processed immediately) or "pending" (queued for processing). You will never see the "otp" status again unless you re-enable OTP.
Listen for transfer webhooks (transfer.success, transfer.failed, transfer.reversed) to know the final outcome of pending transfers. Do not assume a pending transfer will succeed.
When you disable OTP, you remove the last manual confirmation step between your code and your money leaving your Paystack account. This is a serious decision. Here is what you need to have in place before doing it:
If any of these safeguards are not in place, think twice before disabling OTP. The convenience of automated transfers is not worth the risk of losing your entire balance to a leaked key.
If your account is compromised, or you decide the security trade-off is not worth it, re-enable OTP immediately:
async function enableOtp() {
const response = await fetch('https://api.paystack.co/transfer/enable_otp', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log(data);
// { status: true, message: "OTP requirement for transfers has been enabled" }
return data;
}
This takes effect immediately. All future transfers will require OTP again. Any transfers that are already in a "pending" state will still process without OTP, but new transfers will be blocked until the OTP is provided.
If you suspect your account has been compromised:
POST /transfer/enable_otp immediately.In test mode, the disable OTP flow works the same way. You call disable_otp, receive a simulated OTP, and call finalize_disable_otp. No real money moves and no real OTP is sent to your phone.
Test mode and live mode have separate OTP settings. Disabling OTP in test mode does not disable it in live mode. You need to run the process in both environments.
A common workflow:
"otp" status gracefully.Do not hardcode the disable OTP flow in your application startup. It is a one-time manual operation, not something your code should run on every deployment.
OTP still required after calling disable_otp. You called disable_otp but never called finalize_disable_otp. The first call only sends the OTP to you. The second call actually disables it. Check your email or phone for the code and submit it.
Finalize returns "Invalid OTP." The OTP has expired (they are time-limited, usually 30 minutes). Call disable_otp again to get a fresh OTP, then call finalize_disable_otp with the new code.
OTP disabled in test mode but still required in live mode. Test mode and live mode are separate. You need to run the disable flow in both environments using the respective keys.
Transfers stuck in "otp" status. Transfers that were created while OTP was enabled remain in the "otp" status. You cannot retroactively process them by disabling OTP. You have two options: submit the OTP for each pending transfer via POST /transfer/finalize_transfer, or cancel them and create new transfers after OTP is disabled.
Bulk transfers also require OTP. The bulk transfer endpoint (POST /transfer/bulk) follows the same OTP setting as individual transfers. If OTP is enabled, the entire batch will require OTP confirmation. Disable OTP once, and it applies to both individual and bulk transfers.
For more on Paystack transfers and troubleshooting:
Building automated payout systems is a core skill for fintech developers. The McTaba Full-Stack Software and AI Engineering course teaches you to build production payment systems with proper security from day one.
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