Paystack Inline Script Not Loading
The correct script URL is https://js.paystack.co/v2/inline.js (V2) or https://js.paystack.co/v1/inline.js (V1). If the script is not loading, check: (1) The URL in your script tag is correct with no typos. (2) Your Content Security Policy allows scripts from js.paystack.co. (3) No ad blocker or privacy extension is blocking the request. (4) The network request is not failing due to DNS or connectivity issues. Open your browser DevTools Network tab and filter for "paystack" to see exactly what happened to the request.
Symptoms
When the Paystack Inline JS script fails to load, you see one or more of these symptoms:
- Clicking the pay button does nothing
PaystackPop is not definederror in the consoleReferenceError: PaystackPop is not defined- The Network tab shows the script request failed, was blocked, or returned an error
- A Content Security Policy violation in the console
The fastest way to diagnose is to open DevTools (F12), go to the Console tab, and type:
typeof PaystackPop
// "function" = script loaded successfully
// "undefined" = script did not load
If it returns "undefined", the script is not loaded. Go to the Network tab to find out why.
Cause 1: Wrong Script URL
The script URL must be exactly right. There are only two valid URLs:
<!-- V2 (recommended for new integrations) -->
<script src="https://js.paystack.co/v2/inline.js"></script>
<!-- V1 (older, still works) -->
<script src="https://js.paystack.co/v1/inline.js"></script>
Common wrong URLs:
<!-- WRONG: http instead of https -->
<script src="http://js.paystack.co/v2/inline.js"></script>
<!-- WRONG: old or unofficial CDN link -->
<script src="https://cdn.paystack.co/js/inline.js"></script>
<!-- WRONG: typo in domain -->
<script src="https://js.paystck.co/v2/inline.js"></script>
<!-- WRONG: missing version path -->
<script src="https://js.paystack.co/inline.js"></script>
<!-- WRONG: self-hosted copy (will go stale) -->
<script src="/scripts/paystack-inline.js"></script>
Always load the script directly from js.paystack.co. Do not self-host a copy. Paystack updates the script and a self-hosted copy will eventually break.
Cause 2: V1 vs V2 API Differences
If you load V2 but use V1 API syntax (or vice versa), the script loads but your code fails.
V1 usage (with the V1 script):
// V1 pattern
var handler = PaystackPop.setup({
key: 'pk_test_xxxxx',
email: 'customer@email.com',
amount: 50000,
callback: function(response) {
alert('Payment complete: ' + response.reference);
},
onClose: function() {
alert('Transaction was not completed');
},
});
handler.openIframe();
V2 usage (with the V2 script):
// V2 pattern
const paystack = new PaystackPop();
paystack.checkout({
key: 'pk_test_xxxxx',
email: 'customer@email.com',
amount: 50000,
onSuccess: function(transaction) {
console.log('Payment complete:', transaction.reference);
},
onCancel: function() {
console.log('Payment cancelled');
},
});
If you are on V2 but using V1 syntax, the popup may not open. Check which version you loaded and use the matching API pattern. For new projects, use V2.
If you are migrating from V1 to V2, see Migrating from Paystack InlineJS V1 to V2.
Cause 3: Content Security Policy
If your site has a Content Security Policy that does not include Paystack's domain, the browser blocks the script silently.
CSP violation in the console:
Refused to load the script 'https://js.paystack.co/v2/inline.js' because
it violates the following Content Security Policy directive: "script-src 'self'"
Required CSP directives for Paystack:
Content-Security-Policy:
script-src 'self' https://js.paystack.co;
frame-src 'self' https://checkout.paystack.com;
connect-src 'self' https://api.paystack.co;
You need three directives:
script-src: allows loading the Inline JS script from js.paystack.coframe-src: allows the Paystack checkout iframe from checkout.paystack.comconnect-src: allows API calls to api.paystack.co (if you make client-side API calls)
How to find your CSP:
- Open DevTools, go to the Network tab
- Click on your page's main document request
- Look at the Response Headers for
Content-Security-Policy - Also check for a
<meta http-equiv="Content-Security-Policy">tag in your HTML
If you do not have a CSP, this is not the cause. CSP is opt-in. If you never configured one, it is not blocking anything.
Cause 4: Ad Blockers and Privacy Extensions
Ad blockers and privacy extensions are a significant issue for payment scripts. Extensions like uBlock Origin, AdBlock Plus, Privacy Badger, and Brave's built-in shields can block the Paystack script.
These tools use filter lists that target third-party scripts, especially those from payment and tracking domains. Even though Paystack is a legitimate payment processor, the URL pattern matches common blocking rules.
How to verify:
- Open your page in a regular browser window. Does the popup work?
- Open the same page in a private/incognito window with all extensions disabled. Does it work now?
- If it works in incognito, an extension is blocking the script.
What you can do:
You cannot force users to disable their ad blockers. Your best option is to detect when the script is blocked and provide an alternative checkout method.
// Detect if Paystack script loaded
function isPaystackAvailable() {
return typeof PaystackPop !== 'undefined';
}
// Show appropriate UI
function renderPaymentButton() {
const container = document.getElementById('payment-container');
if (isPaystackAvailable()) {
container.innerHTML = '<button id="pay-inline">Pay Now</button>';
document.getElementById('pay-inline').addEventListener('click', openInlineCheckout);
} else {
container.innerHTML =
'<p>Your browser is blocking our payment widget. ' +
'Click below to pay on our secure payment page.</p>' +
'<button id="pay-redirect">Pay on Secure Page</button>';
document.getElementById('pay-redirect').addEventListener('click', redirectToCheckout);
}
}
// Check after the script should have loaded
window.addEventListener('load', renderPaymentButton);
Cause 5: Network Issues
The script load can fail due to network problems:
- No internet connection. Obvious, but worth checking. The script is loaded from Paystack's CDN, not from your server.
- DNS resolution failure. The browser cannot resolve js.paystack.co. This can happen with certain DNS providers or in restricted network environments (corporate firewalls, school networks).
- Request timeout. Slow or unstable connections may cause the script to timeout.
- Paystack CDN outage. Rare, but possible. Check Paystack's status page.
How to check in DevTools:
- Open the Network tab
- Filter by "paystack"
- Look at the status of the script request:
- (blocked:mixed-content): You are loading HTTPS page with HTTP script. Use https:// in the script URL.
- (blocked:csp): Content Security Policy blocked it.
- (blocked:other): Extension or browser setting blocked it.
- (failed) net::ERR_NAME_NOT_RESOLVED: DNS failure.
- (failed) net::ERR_CONNECTION_TIMED_OUT: Network timeout.
- 200 OK: Script loaded fine. Problem is elsewhere.
For network issues on slow African internet connections, add error handling and retry logic to your script loader:
function loadPaystackScript(retries = 3) {
return new Promise((resolve, reject) => {
if (typeof PaystackPop !== 'undefined') {
resolve();
return;
}
const script = document.createElement('script');
script.src = 'https://js.paystack.co/v2/inline.js';
script.async = true;
script.onload = () => resolve();
script.onerror = () => {
document.head.removeChild(script);
if (retries > 0) {
console.warn('Paystack script failed to load. Retrying...');
setTimeout(() => {
loadPaystackScript(retries - 1).then(resolve).catch(reject);
}, 2000);
} else {
reject(new Error('Failed to load Paystack script after retries'));
}
};
document.head.appendChild(script);
});
}
Fallback Strategy: Redirect Checkout
The most robust approach is to build a fallback that works when the Inline JS script cannot load. The Paystack redirect checkout initializes the transaction on your server and redirects the customer to a Paystack-hosted checkout page. No client-side script needed.
Server-side (Node.js/Express):
app.post('/api/checkout', async (req, res) => {
const { email, amount } = req.body;
const 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,
amount,
callback_url: `${process.env.APP_URL}/payment/callback`,
}),
});
const data = await response.json();
if (data.status) {
// Redirect user to Paystack hosted checkout
res.redirect(data.data.authorization_url);
} else {
res.status(400).json({ error: data.message });
}
});
Client-side with automatic fallback:
async function pay(email, amount) {
if (typeof PaystackPop !== 'undefined') {
// Inline JS available, use the popup
const handler = PaystackPop.setup({
key: 'pk_live_xxxxx',
email: email,
amount: amount,
callback: function(response) {
window.location.href = '/payment/verify?ref=' + response.reference;
},
});
handler.openIframe();
} else {
// Inline JS not available, use redirect checkout
const form = document.createElement('form');
form.method = 'POST';
form.action = '/api/checkout';
const emailInput = document.createElement('input');
emailInput.type = 'hidden';
emailInput.name = 'email';
emailInput.value = email;
const amountInput = document.createElement('input');
amountInput.type = 'hidden';
amountInput.name = 'amount';
amountInput.value = amount;
form.appendChild(emailInput);
form.appendChild(amountInput);
document.body.appendChild(form);
form.submit();
}
}
This fallback ensures that customers can always pay, regardless of ad blockers, CSP issues, or network problems with the script CDN. The redirect checkout is slightly less smooth (it leaves your page) but it is reliable.
Framework Loading Patterns
Each frontend framework has a preferred way to load external scripts.
Next.js (App Router):
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Script
src="https://js.paystack.co/v2/inline.js"
strategy="lazyOnload"
onLoad={() => console.log('Paystack script loaded')}
onError={() => console.error('Paystack script failed to load')}
/>
</body>
</html>
);
}
React (dynamic loading):
import { useEffect, useState } from 'react';
export function usePaystack() {
const [ready, setReady] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
if (typeof window.PaystackPop !== 'undefined') {
setReady(true);
return;
}
const script = document.createElement('script');
script.src = 'https://js.paystack.co/v2/inline.js';
script.async = true;
script.onload = () => setReady(true);
script.onerror = () => setError(true);
document.head.appendChild(script);
return () => { document.head.removeChild(script); };
}, []);
return { ready, error };
}
Plain HTML (with load detection):
<script
src="https://js.paystack.co/v2/inline.js"
async
onload="window.paystackReady = true"
onerror="window.paystackFailed = true"
></script>
<script>
// Wait for DOM and script to be ready
window.addEventListener('load', function() {
if (window.paystackFailed || typeof PaystackPop === 'undefined') {
console.warn('Paystack script not available. Enabling fallback.');
document.getElementById('fallback-notice').style.display = 'block';
}
});
</script>
Verification Checklist
After fixing the script loading issue:
- Check script URL is correct. View page source and confirm the script tag has the right URL.
- Verify in DevTools Network tab. The script request should show 200 OK.
- Test PaystackPop in console. Type
typeof PaystackPop. Should return "function". - Test with CSP. If you have a CSP, verify the console shows no CSP violations related to paystack.
- Test in incognito. Confirm the script loads without extensions.
- Test the fallback. Temporarily block the script (add a wrong URL) and verify the redirect checkout works.
- Test on mobile. Load the page on a phone browser and verify the popup opens.
- Test on slow connection. Use DevTools Network throttling (Slow 3G) and verify the script eventually loads or the fallback triggers.
For the complete error reference, see Paystack Errors and Troubleshooting: The Complete Reference. For popup-specific issues after the script loads, see Paystack Popup Not Opening: Frontend Debug Guide.
Key Takeaways
- ✓The correct V2 URL is https://js.paystack.co/v2/inline.js. The V1 URL is https://js.paystack.co/v1/inline.js. Any other URL (like an old CDN link) will not work.
- ✓Content Security Policy script-src must include https://js.paystack.co for the script to load. A CSP violation is silent to the user but visible in the browser console.
- ✓Ad blockers and privacy extensions frequently block payment scripts. You cannot prevent this. Build a fallback redirect flow so payments still work when the script is blocked.
- ✓Check the browser DevTools Network tab to see exactly why the script failed. The error will be one of: blocked by CSP, blocked by extension, DNS failure, timeout, or 404.
- ✓Load the script with the async attribute so it does not block page rendering. Check that PaystackPop is defined before trying to use it.
- ✓If you use a framework (React, Vue, Next.js), consider loading the script dynamically with a load callback so you know when it is ready.
Frequently Asked Questions
- Should I use Paystack Inline JS V1 or V2?
- Use V2 for new projects. The URL is https://js.paystack.co/v2/inline.js. V1 still works but is older and Paystack recommends V2. If you have an existing V1 integration, it will continue to work, but consider migrating to V2 for new features and better support.
- Can I host the Paystack script on my own server?
- No. Always load the script from js.paystack.co. Paystack updates the script to fix bugs, add features, and maintain security. A self-hosted copy will go stale and eventually break. It may also violate Paystack's terms of service.
- What percentage of users have ad blockers that block Paystack?
- It varies by audience. Technical audiences may have ad blocker rates of 30% or higher. General consumer audiences are lower, around 10-20%. Regardless of the percentage, you should build a fallback redirect flow. Even a small percentage of blocked payments means lost revenue.
- Does the async attribute on the script tag matter?
- Yes. Adding async to the script tag means the script loads without blocking page rendering. Without async, the browser pauses rendering until the script loads. Use async and check that PaystackPop is defined before using it, rather than assuming it is available immediately.
- My script loads but PaystackPop is still undefined. What is happening?
- This can happen if your code runs before the script finishes executing. Even after the script file downloads (200 OK in Network tab), it takes a moment to execute and define PaystackPop. Use the script onload event or check for PaystackPop availability in your click handler rather than at page load time.
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