Bonaventure OgetoBy Bonaventure Ogeto|

Migrating from Paystack InlineJS V1 to V2

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.

Why Migrate to V2

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:

  • Server-side initialization by default: V2 is designed around the access code pattern, where your server initializes the transaction and the frontend just opens the popup. This is more secure because your secret key never appears in frontend code, and the amount cannot be tampered with by the customer.
  • Better loading states: V2 handles the popup loading experience more smoothly. The customer sees a proper loading indicator while the checkout form loads.
  • Improved error messages: When something goes wrong (expired session, network error, invalid access code), v2 provides clearer feedback.
  • Ongoing maintenance: New payment channels and checkout features get added to v2 first. If Paystack launches a new payment method, v2 will support it before v1.

The migration is straightforward if you understand the three things that changed: the script URL, the initialization pattern, and the callback names.

V1 Code for Reference

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.

Step-by-Step Migration

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>

API Differences Side by Side

Here are the specific property and method name changes:

ConceptV1V2
Script URLjs.paystack.co/v1/inline.jsjs.paystack.co/v2/inline.js
Create handlerPaystackPop.setup(config)new PaystackPop()
Open checkouthandler.openIframe()popup.checkout(config)
Public key paramkeyNot needed (uses access code)
Amount paramamountNot needed (set server-side)
Reference paramrefNot needed (set server-side)
Access code paramaccess_code (optional)accessCode (primary)
Success callbackcallbackonSuccess
Close callbackonCloseonCancel

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.

Handling the Edge Cases

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.

Testing the Migration

Before switching in production, verify the full flow in test mode.

Checklist:

  1. Your server initializes the transaction using a test secret key (starts with sk_test_)
  2. The frontend loads js.paystack.co/v2/inline.js
  3. The popup opens and shows the test checkout form
  4. You can complete a payment using Paystack test card numbers
  5. The onSuccess callback fires with the correct reference
  6. Your server-side verification works with the test transaction
  7. The onCancel callback fires when you close the popup without paying
  8. Your webhook handler (if configured) receives the test webhook

Test 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.

Rollback Plan

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.

Stay Up to Date

Paystack continues to evolve their inline checkout library. We update this guide when new versions or breaking changes are announced.

Sign up for the McTaba newsletter to get notified about Paystack changes that affect your integration.

Key Takeaways

  • Inline.js v2 uses a different script URL (js.paystack.co/v2/inline.js) and a different initialization pattern. You cannot mix v1 and v2 code.
  • V1 used PaystackPop.setup() with inline configuration (key, email, amount). V2 uses new PaystackPop() with popup.checkout() and an access code from your server.
  • The biggest architectural change: v2 strongly encourages server-side initialization. You pass an accessCode to the popup instead of raw payment parameters.
  • Callback function names changed. V1 used "callback" and "onClose". V2 uses "onSuccess" and "onCancel". Mixing them up causes silent failures.
  • V2 provides better error handling and loading states. The migration is worth doing even if v1 still works, because v2 receives ongoing updates.
  • Test your migration thoroughly in Paystack test mode before switching in production. Use test keys and test card numbers to verify the full flow.

Frequently Asked Questions

Will Paystack Inline.js v1 stop working?
Paystack has not announced an end-of-life date for v1 at the time of writing. However, v2 is the actively maintained version and receives new features and bug fixes first. Migrating now avoids a forced migration later if v1 is deprecated.
Can I load both v1 and v2 scripts on the same page?
This is not recommended and may cause conflicts. The PaystackPop global variable will be overwritten by whichever script loads last. Pick one version and use it consistently across your site.
Does the migration affect my webhook handlers?
No. Webhooks are sent by Paystack servers based on transaction events. They are completely independent of which inline script version your frontend uses. Your webhook handlers do not need any changes.
Do I need to change my Paystack API keys for v2?
No. Your existing public and secret keys work with both v1 and v2. The keys are tied to your Paystack account, not to a specific inline script version.
What if my v1 code was using the amount directly in the frontend?
The migration is a good opportunity to fix this security issue. Move the amount to your server-side initialization call so customers cannot tamper with it in the browser. V2 is designed around this server-first pattern.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon