Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Test Mode Working but Live Mode Failing

When Paystack works in test but fails in live, the cause is almost always one of these: your business account is not fully activated (Paystack requires identity verification and business documents before enabling live payments), you are still using test keys (sk_test_) instead of live keys (sk_live_), your webhook URL is not HTTPS (Paystack requires HTTPS for live webhooks), your domain is not verified in the Paystack dashboard, or your callback URL uses HTTP instead of HTTPS. Run through the checklist in this guide to find the exact issue.

What "Live Mode Failing" Looks Like

You have been testing your Paystack integration for days. Test cards work. Webhooks fire. Verification succeeds. You switch to live keys, and suddenly:

  • The API returns { "status": false, "message": "Invalid key" }
  • Transactions initialize but fail during payment with no clear error
  • Webhooks stop arriving at your server
  • The checkout page shows an error or loads a blank Paystack page
  • Real card payments are declined with "Transaction failed"
  • The API returns { "status": false, "message": "Account not activated" }

The fix depends on which of these symptoms you see. Walk through the checklist below from top to bottom. Each item takes under two minutes to check.

Check 1: Is Your Business Account Activated?

This is the most common cause by far. Paystack allows you to develop with test keys before your account is activated, but live keys only work after activation is complete.

To check your activation status:

  1. Log into your Paystack dashboard
  2. Look at the top of the page. If you see a banner saying "Complete your business registration" or "Activate your account", your account is not activated
  3. Go to Settings > Business Settings and check if all required fields are completed

Paystack activation requirements (for Nigerian businesses):

  • Business name and registration number (CAC number)
  • Business address
  • Director/owner identity verification (BVN or valid ID)
  • Bank account details for settlement
  • Business category and description

For other countries (Ghana, South Africa, Kenya), the requirements are similar but use local business registration and identity systems.

Activation usually takes 24 to 48 hours after submitting all documents. Some accounts activate within hours. If it has been more than 48 hours, contact Paystack support at support@paystack.com with your business name and registered email.

The fix: Complete all required fields in your business settings and wait for activation. You will receive an email when your account is activated. Until then, continue developing in test mode.

Check 2: Are You Using Live Keys?

This sounds obvious, but it trips up more developers than you would expect. Your code must switch from test keys to live keys in two places: the server (secret key) and the frontend (public key).

// Check which keys your server is using
const key = process.env.PAYSTACK_SECRET_KEY?.trim();
console.log('Key prefix:', key?.substring(0, 8));
// Must show "sk_live_" for live mode

// In your frontend, check the public key
// Your PaystackPop.setup or script tag should use pk_live_, not pk_test_

Common mistakes:

  • Server has live key, frontend has test key. The server initializes the transaction in live mode, but the frontend popup uses a test public key. The popup will not load or will show an error.
  • Frontend has live key, server has test key. The server initializes the transaction in test mode, generating a test authorization_url. But the customer has a real card. Test transactions with real cards still "succeed" in test mode, but no real money moves. You think it works but it does not.
  • .env file has the right key, but the deployment uses the old key. You updated your local .env file but forgot to update the environment variable on Vercel, Railway, or wherever your production server runs.

The fix: Verify keys in both places.

// Server startup check
function verifyLiveMode() {
  const secretKey = process.env.PAYSTACK_SECRET_KEY?.trim();
  const publicKey = process.env.PAYSTACK_PUBLIC_KEY?.trim();

  if (!secretKey?.startsWith('sk_live_')) {
    console.error('WARNING: Server is NOT using live Paystack keys');
    console.error(`Current prefix: ${secretKey?.substring(0, 8)}`);
  }

  if (publicKey && !publicKey.startsWith('pk_live_')) {
    console.error('WARNING: Public key is NOT a live key');
    console.error(`Current prefix: ${publicKey?.substring(0, 8)}`);
  }

  if (secretKey?.startsWith('sk_live_') && publicKey?.startsWith('pk_live_')) {
    console.log('Paystack live mode confirmed');
  }
}

verifyLiveMode();

Check 3: Is Your Webhook URL HTTPS?

Paystack accepts HTTP webhook URLs in test mode. In live mode, it requires HTTPS. If your webhook URL starts with http://, Paystack will not deliver webhooks in live mode.

To check your webhook URL:

  1. Go to your Paystack dashboard
  2. Navigate to Settings > API Keys & Webhooks
  3. Check the Webhook URL field
  4. Make sure it starts with https://
// Correct live webhook URL
https://yoursite.com/api/webhooks/paystack

// WRONG: HTTP will not work in live mode
http://yoursite.com/api/webhooks/paystack

// WRONG: localhost will not work in live mode
http://localhost:3000/api/webhooks/paystack

Your webhook URL must:

  • Use HTTPS (not HTTP)
  • Point to a publicly accessible server (not localhost)
  • Return a 200 status code within 10 seconds
  • Be a real endpoint that accepts POST requests

If you have not set up HTTPS yet, see Paystack Mixed Content and HTTPS Errors for a guide on getting a free SSL certificate.

The fix: Update your webhook URL in the Paystack dashboard to use HTTPS. Make sure the endpoint is deployed and reachable before changing the URL.

Check 4: Is Your Callback URL HTTPS?

Similar to webhook URLs, Paystack requires HTTPS for callback URLs in live mode. If your callback_url uses HTTP, the redirect after payment may fail silently.

// BROKEN in live mode: HTTP callback_url
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: 'customer@example.com',
    amount: 500000,
    callback_url: 'http://yoursite.com/payment/verify',  // HTTP won't work in live mode
  }),
});

// FIXED: HTTPS callback_url
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: 'customer@example.com',
    amount: 500000,
    callback_url: 'https://yoursite.com/payment/verify',  // HTTPS required
  }),
});

Also check the default callback URL in your Paystack dashboard under Settings > API Keys & Webhooks. If it uses HTTP, update it to HTTPS.

Check 5: Is Your Domain Verified?

Paystack may require domain verification for live transactions. This confirms that you own the domain where the checkout popup or redirect will run.

To verify your domain:

  1. Go to your Paystack dashboard
  2. Navigate to Settings > Domains
  3. Add your production domain (e.g., yoursite.com)
  4. Follow the verification steps (usually adding a DNS record or a meta tag)

Domain verification is not always required. It depends on your account type and risk profile. But if your live transactions are failing and everything else on this checklist is correct, unverified domain could be the issue. Add and verify your domain to rule it out.

If you use multiple domains (e.g., yoursite.com and app.yoursite.com), verify each one separately.

Check 6: Are Your Environment Variables Correct on the Production Server?

The most frustrating version of this problem: your code is correct, your local .env file has live keys, but your production server is still using test keys. This happens when you update your local file but forget to update the deployment platform.

Check your production environment variables:

# Vercel
vercel env ls
# Look for PAYSTACK_SECRET_KEY and PAYSTACK_PUBLIC_KEY
# Verify they start with sk_live_ and pk_live_

# Railway
railway variables
# Check the Paystack-related variables

# Render
# Check the Environment tab in your Render dashboard

# Heroku
heroku config
# Look for Paystack key values

A common trap: multiple environments. Many platforms let you set different environment variables for production, preview, and development. If you set your live key only for "production" but your deployment is tagged as "preview", it will use the test key.

# Vercel: set for production specifically
vercel env add PAYSTACK_SECRET_KEY production
# Enter: sk_live_xxxxxxxxxxxxxxxx

# Verify it is set for production
vercel env ls production

Another trap: stale deployments. If you change environment variables on your platform without redeploying, the running server still uses the old values. Most platforms require a redeploy for environment variable changes to take effect.

# Force a redeploy after changing environment variables
# Vercel: push a commit or click "Redeploy" in the dashboard
# Railway: click "Redeploy" in the dashboard
# Render: deployments redeploy automatically on env var changes

The Complete Go-Live Checklist

Before switching from test to live, run through this checklist. Each item takes under a minute.

// Go-live checklist for Paystack integration

const goLiveChecklist = [
  {
    item: 'Business account activated',
    check: 'Dashboard shows no activation banner. Live keys are available.',
  },
  {
    item: 'Server uses sk_live_ secret key',
    check: 'Log key prefix at startup. Confirm sk_live_.',
  },
  {
    item: 'Frontend uses pk_live_ public key',
    check: 'Inspect checkout popup or script tag. Confirm pk_live_.',
  },
  {
    item: 'Webhook URL is HTTPS',
    check: 'Dashboard > Settings > API Keys & Webhooks. URL starts with https://.',
  },
  {
    item: 'Webhook endpoint returns 200',
    check: 'curl -X POST your webhook URL. Should return 200.',
  },
  {
    item: 'Callback URL is HTTPS',
    check: 'callback_url in transaction/initialize starts with https://.',
  },
  {
    item: 'Domain is verified',
    check: 'Dashboard > Settings > Domains. Domain shows verified.',
  },
  {
    item: 'Production env vars are updated',
    check: 'Check deployment platform. Verify live keys are set for production.',
  },
  {
    item: 'Production server is redeployed',
    check: 'Redeploy after changing env vars.',
  },
  {
    item: 'Test transaction with real card',
    check: 'Pay 100 NGN with a real card. Verify webhook, callback, and verification all work.',
  },
  {
    item: 'Refund the test transaction',
    check: 'Refund the 100 NGN from the Paystack dashboard to confirm refunds work.',
  },
];

// Log incomplete items
goLiveChecklist.forEach(({ item, check }) => {
  console.log(`[ ] ${item}`);
  console.log(`    How: ${check}`);
});

Verification: Confirm Live Mode Works

After completing the checklist, verify live mode works end to end:

Step 1: Verify the live key is valid.

// Run from your server (not the browser)
async function verifyLiveKey() {
  const key = process.env.PAYSTACK_SECRET_KEY?.trim();

  if (!key?.startsWith('sk_live_')) {
    console.error('Not a live key:', key?.substring(0, 8));
    return;
  }

  const res = await fetch('https://api.paystack.co/transaction?perPage=1', {
    headers: { Authorization: `Bearer ${key}` },
  });

  const data = await res.json();

  if (res.status === 200 && data.status) {
    console.log('Live key is valid and working');
  } else {
    console.error('Live key rejected:', data.message);
  }
}

verifyLiveKey();

Step 2: Initialize a real transaction.

Use your production server to initialize a transaction for a small amount (100 NGN or equivalent). Confirm you get an authorization_url back. Open the URL in your browser and confirm the Paystack checkout page loads.

Step 3: Complete a real payment.

Use a real debit card to pay the small amount. Confirm:

  • The payment succeeds on the Paystack checkout page
  • You are redirected to your callback URL
  • Your webhook endpoint receives the charge.success event
  • Your verification endpoint confirms the transaction as successful
  • The transaction appears in your Paystack dashboard under live transactions

Step 4: Refund the test payment.

In the Paystack dashboard, find the test transaction and initiate a refund. This confirms refunds work and returns the money to your card.

Key Takeaways

  • The number one reason live mode fails is that your Paystack business account is not fully activated. Paystack requires identity verification and business registration documents before enabling live payments.
  • Test keys (sk_test_, pk_test_) and live keys (sk_live_, pk_live_) are completely separate. Your server and frontend must both switch to live keys for live mode to work.
  • Paystack requires HTTPS for live webhook URLs. A webhook URL starting with http:// will be rejected in live mode even though it works in test mode.
  • Your callback_url must also use HTTPS in live mode. HTTP callback URLs work in test mode but fail silently in live mode.
  • Domain verification in the Paystack dashboard confirms you own the domain where your checkout will run. Without it, live transactions from that domain may be blocked.
  • After switching to live mode, make a real test transaction with a small amount (like 100 NGN) using a real card to confirm everything works end to end.
  • Keep your test and live environment variables separate. Use your deployment platform's environment variable management to ensure each environment gets the right keys automatically.

Frequently Asked Questions

How long does Paystack business activation take?
Typically 24 to 48 hours after you submit all required documents. Some accounts activate within hours. The most common delay is missing or incorrect documents. Make sure your CAC number, BVN, and bank account details are accurate before submitting.
Can I process real payments before my Paystack account is activated?
No. Live payment processing is only available after your business account is fully activated. You can develop and test your entire integration using test mode keys, but real money transactions require activation.
Do I need separate test and live webhook URLs?
It is recommended. In test mode, Paystack sends webhooks to the URL in your dashboard (which can be HTTP). In live mode, it sends to the same URL (which must be HTTPS). Some developers use the same URL for both, but using separate URLs for test and live prevents test webhooks from triggering live business logic. Paystack sends webhooks for both modes to the same URL unless you change it when switching modes.
My live key returns "Invalid key" even though I just copied it from the dashboard. Why?
Check for trailing whitespace. When you copy a key from the Paystack dashboard, you might accidentally select invisible whitespace characters. Trim the key with .trim() in your code. Also confirm you are looking at the live key section of the dashboard, not the test key section. See our guide on Paystack Live Keys Rejected After Business Activation for more causes.
I switched to live keys but my test transactions still show up. Is something wrong?
Test and live transactions are separate in the Paystack dashboard. If you are seeing test transactions, switch the dashboard view to "Live" mode using the toggle at the top. If you initialized a transaction with a test key, it will always be a test transaction regardless of whether the card is real.

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