Paystack supports preauthorization through a two-step flow: first charge a small amount (like 50 NGN) to validate the card and get an authorization code, then charge the real amount later using that authorization code. You can also use partial debit to attempt a charge and capture only what the card allows. Full hold-and-capture (like Stripe auth and capture) is not natively supported in the same way, so you work with authorization codes.
Traditional payment processors offer a two-phase card charge: authorize (hold the funds) and capture (take the funds). Paystack works differently. There is no explicit "hold" endpoint that freezes funds on a customer's card without moving money.
Instead, Paystack gives you authorization codes. When a customer pays with a card, the verify response includes an authorization object with an authorization_code. You can use this code to charge the same card again in the future without the customer re-entering details.
The preauthorization pattern on Paystack is:
This is not a true hold (the customer's available balance is not reduced until you charge), but it verifies the card and gives you the ability to charge later. It is how ride-hailing apps, hotel booking platforms, and equipment rental services implement "save card for later" flows on Paystack.
Step 1: Initialize a transaction for a small validation amount.
// Server: initialize a validation charge
app.post('/api/validate-card', async function(req, res) {
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: req.body.email,
amount: 5000, // 50 NGN - validation amount
currency: 'NGN',
channels: ['card'], // Card only for preauth
metadata: {
type: 'card_validation',
user_id: req.body.userId,
},
}),
});
var data = await response.json();
res.json({
access_code: data.data.access_code,
reference: data.data.reference,
});
});
Step 2: After the customer pays, verify and extract the authorization code.
// Webhook handler: capture the authorization code
app.post('/webhooks/paystack', async function(req, res) {
res.sendStatus(200);
var event = req.body;
if (event.event === 'charge.success') {
var metadata = event.data.metadata || {};
if (metadata.type === 'card_validation') {
// Save the authorization code for later charges
var authCode = event.data.authorization.authorization_code;
var cardBin = event.data.authorization.bin;
var cardLast4 = event.data.authorization.last4;
var cardBank = event.data.authorization.bank;
await db.savedCards.create({
user_id: metadata.user_id,
authorization_code: authCode,
card_display: cardBin + '****' + cardLast4,
bank: cardBank,
});
// Refund the validation charge
await fetch('https://api.paystack.co/refund', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
transaction: event.data.reference,
}),
});
}
}
});
Step 3: When you know the final amount, charge the saved card.
// Charge the saved card later
async function chargeCustomer(userId, amountInKobo) {
var savedCard = await db.savedCards.findByUserId(userId);
var response = await fetch('https://api.paystack.co/transaction/charge_authorization', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
authorization_code: savedCard.authorization_code,
email: savedCard.email,
amount: amountInKobo,
currency: 'NGN',
}),
});
var data = await response.json();
if (data.data.status === 'success') {
return { success: true, reference: data.data.reference };
} else {
return { success: false, message: data.data.gateway_response };
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Pattern 1: Refundable deposit
Charge the full deposit amount, save the authorization code, and refund the deposit when the customer returns the item or completes the service. This works for equipment rentals, key deposits, or event catering deposits.
// Charge deposit
var depositResult = await initializeAndRedirect({
email: customer.email,
amount: 1000000, // 10,000 NGN deposit
metadata: {
type: 'rental_deposit',
rental_id: rental.id,
},
});
// Later, when rental is returned undamaged
await fetch('https://api.paystack.co/refund', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
transaction: rental.deposit_reference,
}),
});
Pattern 2: Validate now, charge later
Charge a token amount (50 NGN), refund it immediately, then charge the real amount when the service is complete. This is the ride-hailing pattern: the app validates the card when the customer books, then charges the final fare after the trip.
Pattern 3: Charge the estimate, refund the difference
When the final amount is lower than the initial charge (the estimate was higher), you can issue a partial refund for the difference. When the final amount is higher, you charge the difference using the saved authorization code.
Authorization codes give you the power to charge a customer's card without their direct interaction. This comes with significant responsibility.
For disputes and chargebacks, see the developer playbook for disputes.
The validation charge pattern can confuse customers if not communicated well. Here is what good communication looks like:
Before the validation charge: Tell the customer exactly what will happen. "We will charge NGN 50 to verify your card. This charge will be refunded within 24 hours."
After the validation charge: Confirm the card was saved. "Your card ending in 4081 has been saved. The NGN 50 validation charge will be refunded."
Before a deferred charge: Notify the customer. "Your trip has ended. We are charging NGN 2,500 to your card ending in 4081."
After a charge failure: Tell the customer their card needs updating. "We could not charge your saved card. Please update your payment method to continue."
The deposit pattern is clearer because the customer expects to pay the deposit. Still, confirm when the deposit will be refunded and the conditions for keeping it.
If the preauthorization pattern feels heavy for your use case, consider these alternatives:
For wallet and top-up flows that use saved cards, see building a wallet top-up flow. For the complete guide to the Charge API that powers deferred charges, see Charge API for custom checkout.
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