Redirect checkout sends the customer to checkout.paystack.com and brings them back to your callback URL after payment. Inline checkout opens a popup iframe on your page using PaystackPop. Choose redirect for simpler implementation and better mobile browser compatibility. Choose inline for a seamless on-site experience. Both are equally secure and support the same payment channels.
Redirect checkout is the simplest path. Your server initializes the transaction, gets back an authorization_url, and your frontend sends the customer there. The customer leaves your site entirely, completes payment on Paystack's hosted page, and Paystack redirects them back to your callback_url with the reference as a query parameter.
// Server: initialize the transaction
app.post('/api/pay', 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: req.body.amount * 100,
callback_url: 'https://yoursite.com/payment/callback',
}),
});
var data = await response.json();
// Send the authorization URL to the frontend
res.json({ url: data.data.authorization_url });
});
// Frontend: redirect the customer
fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, amount: amount }),
})
.then(function(res) { return res.json(); })
.then(function(data) {
window.location.href = data.url;
});
After payment, the customer lands on https://yoursite.com/payment/callback?trxref=REF&reference=REF. Your server reads the reference, calls the Paystack verify endpoint, and grants value if the payment succeeded.
The entire frontend code is one line: window.location.href = url. No JavaScript library to load, no popup to manage, no iframe to worry about.
Inline checkout opens the Paystack checkout form in a popup iframe on your page. The customer sees the checkout overlaid on top of your site. When they finish, you get a JavaScript callback instead of a page redirect.
<script src="https://js.paystack.co/v2/inline.js"></script>
<script>
async function payInline() {
// Step 1: Get access code from your server
var response = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, amount: amount }),
});
var data = await response.json();
// Step 2: Open the popup
var popup = new PaystackPop();
popup.checkout({
accessCode: data.access_code,
onSuccess: function(transaction) {
// Step 3: Verify on your server
verifyPayment(transaction.reference);
},
onCancel: function() {
console.log('Customer closed the popup');
},
});
}
</script>
The customer stays on your page throughout. The Paystack checkout form appears as a modal overlay. When the customer completes payment, the popup closes and your onSuccess function runs with the transaction reference. You then verify server-side.
This requires loading the Paystack Inline.js script, initializing the transaction on your server (to get the access code), and handling two callbacks (success and cancel). It is more code than redirect, but the customer never leaves your domain visually.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Redirect checkout is the better choice in these situations:
Mobile-first applications
Mobile browsers handle popup iframes inconsistently. Some browsers block them. Others render them at awkward sizes. Some interfere with keyboard input when the customer types card numbers. Redirect checkout avoids all of these issues because the customer is on a full-page Paystack checkout designed for mobile screens.
Server-rendered applications
If you are building with a server-rendered framework (plain Express with EJS templates, Django, Laravel, Rails), redirect checkout fits naturally. You do not need to add a JavaScript build step or load a client-side library. The server generates the authorization URL and returns a redirect response.
Minimal frontend code
If you want the simplest possible integration with the least frontend JavaScript, redirect checkout wins. There is no library to load, no popup state to manage, and no callback functions to wire up.
Regulatory or compliance requirements
Some compliance frameworks prefer that the payment page is clearly on the payment processor's domain. Redirect checkout makes this obvious to the customer: they can see "checkout.paystack.com" in the browser address bar.
Inline checkout is the better choice in these situations:
Single-page applications (SPAs)
If you are building a React, Vue, or Angular app where the entire experience happens on one page, a full-page redirect breaks the flow. The customer leaves your app, pays, and then has to reload your entire SPA when they return. Inline checkout keeps them in the app.
Checkout conversion optimization
When the customer stays on your site, the visual continuity can reduce drop-off. They see your branding in the background. The checkout feels like part of your purchase flow rather than a handoff to a third party. For established brands where the customer already trusts the site, this can improve completion rates.
Post-payment user experience
With inline checkout, you get a JavaScript callback the instant payment completes. You can update the UI immediately: show a success animation, clear the cart, redirect to a confirmation page, or trigger a download. With redirect checkout, the customer waits for a page load after being redirected back.
Multi-step checkout flows
If payment is one step in a longer flow (like a wizard with shipping, review, payment, and confirmation steps), inline checkout lets you keep the customer in the wizard. After payment, you advance them to the confirmation step without a page reload.
Both approaches are equally secure. Here is why:
GET /transaction/verify/{reference} to confirm the payment before granting value.charge.success webhook from Paystack. Webhooks are the authoritative confirmation and do not depend on the checkout method.The inline popup runs in a sandboxed iframe on Paystack's domain. Your page cannot read anything inside that iframe (card numbers, OTPs, etc.) due to browser same-origin policy. The iframe communicates with your page only through the structured callback messages (success, cancel).
One nuance: if you are using the v2 inline library with the newTransaction method (passing amount and key directly in the frontend instead of using an access code), a technically skilled customer could modify the amount in the browser console before the popup opens. Always use the access code pattern for production, where the amount is locked server-side. See why you must never call the Paystack API from your frontend for more detail.
Mobile is where the two approaches diverge most in practice.
Redirect on mobile: Works reliably. The customer gets a full-screen Paystack checkout optimized for mobile. Keyboard input, 3DS challenge screens, and bank OTP pages all render at full width. After payment, the customer is redirected back to your callback URL and your mobile-responsive page loads.
Inline on mobile: Can be problematic. The popup iframe may not render at the correct size. On some Android browsers, the keyboard pushes the iframe content out of view when the customer types. iOS Safari sometimes blocks third-party iframes or cookies, which can interfere with the checkout session. 3DS challenge pages that open in a new tab may lose the connection back to the popup.
If your audience is primarily mobile (which is the majority in most African markets), redirect checkout is the safer default. If you want inline checkout on desktop but reliable behavior on mobile, detect the device and switch approaches:
function isMobile() {
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
}
async function handlePayment() {
var response = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, amount: amount }),
});
var data = await response.json();
if (isMobile()) {
// Redirect on mobile
window.location.href = data.authorization_url;
} else {
// Popup on desktop
var popup = new PaystackPop();
popup.checkout({
accessCode: data.access_code,
onSuccess: function(tx) { verifyPayment(tx.reference); },
onCancel: function() { console.log('Cancelled'); },
});
}
}
This gives you the best of both worlds: seamless inline checkout on desktop and reliable redirect checkout on mobile.
Regardless of which approach you use, the post-payment logic is the same: verify the transaction server-side, then grant value. The difference is how you trigger that verification.
After redirect checkout: The customer lands on your callback URL with a reference query parameter. Your server reads it, calls verify, and renders the appropriate page.
After inline checkout: Your onSuccess callback fires with the transaction object. You send the reference to your server (via fetch or by redirecting to a verification page), your server calls verify, and you update the UI.
In both cases, the webhook may arrive before or after the redirect/callback. Your fulfillment logic should be idempotent: if the webhook already fulfilled the order, the redirect/callback verification should see that and skip re-fulfillment. If the redirect/callback fulfilled first, the webhook handler should see that and skip.
For more on handling the race between webhooks and callbacks, see the complete accepting payments guide.
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