To migrate from Paystack Inline.js v1 to v2: change the script src from js.paystack.co/v1/inline.js to js.paystack.co/v2/inline.js, replace PaystackPop.setup() with new PaystackPop() followed by popup.checkout(), move from passing your public key and amount directly to using a server-generated access code, and update your callback/close handlers to the new onSuccess/onCancel signature.
Paystack Inline.js v1 still works at the time of writing, but v2 is the version that receives updates, new features, and bug fixes. Here is what you gain by migrating:
The migration is straightforward if you understand the three things that changed: the script URL, the initialization pattern, and the callback names.
Here is what a typical v1 integration looks like. If your code resembles this, you are on v1 and should migrate.
<!-- V1: Old script URL -->
<script src="https://js.paystack.co/v1/inline.js"></script>
<script>
// V1: Setup pattern with inline configuration
var handler = PaystackPop.setup({
key: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxx',
email: 'customer@example.com',
amount: 500000, // 5000 NGN in kobo
currency: 'NGN',
ref: 'order_142_' + Date.now(),
callback: function(response) {
// Payment complete
alert('Payment successful. Reference: ' + response.reference);
},
onClose: function() {
// Customer closed the popup
alert('Transaction was not completed');
},
});
// V1: Open the popup
handler.openIframe();
</script>
The problems with this pattern are subtle but real. The public key, email, and amount are all in the frontend JavaScript. A knowledgeable user can open the browser console, change the amount to 100 (1 Naira), and submit. If your backend does not verify the amount after payment, the customer gets the product for nearly nothing.
V1 does support passing an access code instead of inline parameters, but the documentation and most tutorials show the inline pattern. V2 makes the access code pattern the default.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Step 1: Update the script URL
Change your script tag from:
<!-- Old -->
<script src="https://js.paystack.co/v1/inline.js"></script>
<!-- New -->
<script src="https://js.paystack.co/v2/inline.js"></script>
Step 2: Add server-side initialization (if you have not already)
If your v1 code was passing the amount and email directly in the frontend, you need a server endpoint that initializes the transaction. This is the most important change in the migration.
// Server: POST /api/initialize-payment
var express = require('express');
var app = express();
app.use(express.json());
app.post('/api/initialize-payment', async function(req, res) {
var email = req.body.email;
var amountInNaira = req.body.amount;
var orderId = req.body.orderId;
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: email,
amount: amountInNaira * 100,
reference: 'order_' + orderId,
callback_url: 'https://yoursite.com/payment/verify',
}),
});
var data = await response.json();
if (data.status) {
res.json({
access_code: data.data.access_code,
reference: data.data.reference,
});
} else {
res.status(400).json({ error: data.message });
}
});
Step 3: Update the frontend initialization
<script src="https://js.paystack.co/v2/inline.js"></script>
<script>
async function payWithPaystack() {
// Get access code from your server
var response = await fetch('/api/initialize-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'customer@example.com',
amount: 5000,
orderId: '142',
}),
});
var data = await response.json();
// V2: Create instance and call checkout
var popup = new PaystackPop();
popup.checkout({
accessCode: data.access_code,
onSuccess: function(transaction) {
// Payment complete - verify on server
window.location.href = '/payment/verify?reference=' + transaction.reference;
},
onCancel: function() {
console.log('Customer closed checkout');
},
});
}
</script>
Here are the specific property and method name changes:
| Concept | V1 | V2 |
|---|---|---|
| Script URL | js.paystack.co/v1/inline.js | js.paystack.co/v2/inline.js |
| Create handler | PaystackPop.setup(config) | new PaystackPop() |
| Open checkout | handler.openIframe() | popup.checkout(config) |
| Public key param | key | Not needed (uses access code) |
| Amount param | amount | Not needed (set server-side) |
| Reference param | ref | Not needed (set server-side) |
| Access code param | access_code (optional) | accessCode (primary) |
| Success callback | callback | onSuccess |
| Close callback | onClose | onCancel |
The underscore vs camelCase change in access_code to accessCode is easy to miss. If you pass access_code (with underscore) to v2, it will be silently ignored and the popup will not load correctly.
The callback name change from callback to onSuccess is the other common gotcha. If you keep the old name, v2 will open the checkout but your success handler will never fire.
What if I was already using access codes in v1?
If your v1 code was already doing server-side initialization and passing access_code to PaystackPop.setup(), the migration is simpler. You only need to update the script URL, change the initialization pattern, and rename the callback functions. The server-side code stays the same.
What about the newTransaction method?
V2 also supports popup.newTransaction(config) for cases where you want to pass payment parameters directly (similar to v1). This method accepts key, email, amount, and other parameters inline. However, this approach has the same security concerns as v1 inline configuration. Use popup.checkout() with an access code for production integrations.
// V2 newTransaction - works but not recommended for production
var popup = new PaystackPop();
popup.newTransaction({
key: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxx',
email: 'customer@example.com',
amount: 500000,
onSuccess: function(transaction) {
verifyPayment(transaction.reference);
},
onCancel: function() {
console.log('Cancelled');
},
});
What if I have multiple checkout buttons on one page?
In v1, you might have created one handler and reused it. In v2, create a new PaystackPop instance for each checkout or reuse the same instance with different popup.checkout() calls. Each call to popup.checkout() opens a new checkout session.
What about React, Vue, or Angular wrappers?
If you are using a framework-specific Paystack wrapper library (like react-paystack or vue-paystack), check whether the wrapper has been updated for v2. Some wrappers handle the v1-to-v2 differences internally. If the wrapper has not been updated, you can use v2 directly by loading the script and calling the API from your component.
Before switching in production, verify the full flow in test mode.
Checklist:
sk_test_)js.paystack.co/v2/inline.jsonSuccess callback fires with the correct referenceonCancel callback fires when you close the popup without payingTest with different payment channels if you support them. Mobile money and bank transfer flows in test mode behave slightly differently from card flows, so run through each one.
Once everything works in test mode, switch to your live keys. The v2 script URL and the JavaScript API are the same in both test and live modes. Only the keys and the transaction behavior differ.
If something goes wrong after deploying v2, you can roll back by reverting the script URL and the JavaScript code. The server-side initialization code does not need to change because both v1 and v2 use the same Paystack API endpoints. The initialize endpoint, the verify endpoint, and the webhook format are identical regardless of which inline script version you use on the frontend.
Keep your v1 code in version control so you can revert quickly if needed. In practice, the migration rarely needs a rollback because the failure modes are obvious during testing: either the popup opens and works, or it does not.
For the complete picture of how Paystack popup checkout works under the hood, see Inline.js explained line by line. For deciding between popup and redirect checkout, see redirect vs inline 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