Paystack Invalid Key: Every Cause and Fix
The "Invalid key" error means your Authorization header contains a key Paystack does not recognise. The five causes: you used a public key where a secret key is needed, you sent a test key against the live API (or vice versa), your key has trailing whitespace or a newline from your .env file, the key belongs to a different Paystack account, or the key was regenerated and the old one expired. Check the key prefix (sk_test_ or sk_live_), trim whitespace, and confirm the key in your dashboard.
The Error Message
When Paystack rejects your API key, the response looks like this:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"status": false,
"message": "Invalid key"
}
You will see this on any endpoint: /transaction/initialize, /transfer, /customer, or any other. The error is not endpoint-specific. It fires before Paystack even looks at your request body. The API checked your Authorization header, found a key it does not recognise, and stopped right there.
Some developers confuse this with a 403 Forbidden. They are different. A 403 means Paystack knows who you are but you do not have permission for that action. A 401 with "Invalid key" means Paystack does not know who you are at all.
Cause 1: Using a Public Key Where a Secret Key Is Needed
This is the most common cause. Paystack gives every account two key types:
- Public keys start with
pk_test_orpk_live_. They are meant for the frontend only, specifically for initialising the Paystack checkout popup via Inline.js. - Secret keys start with
sk_test_orsk_live_. They are meant for server-side API calls.
If you put a public key in your server's Authorization header, Paystack rejects it immediately.
// BROKEN: public key on the server
const response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer pk_test_abc123def456', // wrong key type
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: 'user@example.com', amount: 50000 }),
});
// Response: { "status": false, "message": "Invalid key" }
The fix:
// FIXED: secret key on the server
const response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`, // sk_test_ or sk_live_
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: 'user@example.com', amount: 50000 }),
});
How to tell which you have: Look at the first 8 characters. If it starts with pk_, it is a public key. If it starts with sk_, it is a secret key. No exceptions.
Cause 2: Test Key in Live Mode (or Vice Versa)
Paystack test keys and live keys are completely separate. A test key cannot authenticate against the live environment, and a live key cannot authenticate against the test environment. Both environments use the same base URL (https://api.paystack.co), so there is no URL difference to tip you off.
This usually happens during deployment. You develop with sk_test_ keys, deploy to production, and forget to switch to sk_live_ keys. Or the reverse: you are debugging production issues locally and accidentally point your dev server at a live key.
The fix: Match your key environment to your intent.
# .env.development
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxx
# .env.production
PAYSTACK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxx
Use your deployment platform's environment variable management (Vercel, Railway, Render all have this) instead of .env files on the server. That way each environment gets the right key automatically.
Quick diagnostic: Log the key prefix before any API call in your error handler.
function logKeyPrefix(key: string) {
// Safe: only shows prefix, never the full key
const prefix = key.substring(0, 12);
console.log(`Using Paystack key starting with: ${prefix}`);
// Output: "Using Paystack key starting with: sk_test_xxxx" or "sk_live_xxxx"
}
Cause 3: Extra Whitespace or Newline in the Key
This one is invisible and infuriating. Your .env file looks correct. The key is correct. But there is a trailing space, tab, or newline character after the key value that your eyes cannot see.
# This .env line has a trailing space you cannot see
PAYSTACK_SECRET_KEY=sk_test_abc123def456
When your app loads this value, the space becomes part of the key string. Paystack receives "sk_test_abc123def456 " instead of "sk_test_abc123def456" and rejects it.
Common sources of this problem:
- Copying the key from the Paystack dashboard and accidentally selecting trailing whitespace
- Some text editors add a newline at the end of files
- Pasting from Slack or email sometimes adds invisible characters
- Windows line endings (
\r\n) in .env files on Linux servers
The fix: Trim the key at load time.
// Node.js / Express
const PAYSTACK_SECRET_KEY = process.env.PAYSTACK_SECRET_KEY?.trim();
if (!PAYSTACK_SECRET_KEY) {
throw new Error('PAYSTACK_SECRET_KEY is not set');
}
// Python / Django
import os
PAYSTACK_SECRET_KEY = os.environ.get('PAYSTACK_SECRET_KEY', '').strip()
# PHP / Laravel
$paystackKey = trim(env('PAYSTACK_SECRET_KEY'));
Diagnostic: Check the actual bytes.
// Shows invisible characters
const key = process.env.PAYSTACK_SECRET_KEY || '';
console.log('Key length:', key.length);
console.log('Trimmed length:', key.trim().length);
console.log('Last char code:', key.charCodeAt(key.length - 1));
// If lengths differ, you have trailing whitespace
Cause 4: Key from a Different Paystack Account
If you manage multiple Paystack accounts (your own, a client's, a staging account), it is easy to grab the key from the wrong dashboard. Each Paystack business account has its own set of keys. The secret key from Account A will return "Invalid key" when used against Account B's integration.
This often happens when:
- You switch between client projects and copy the wrong key
- Your team shares keys over Slack and someone pastes one from a different project
- You have a personal Paystack account and a company account
- You created a new Paystack business for a different product but used the old key in the new project
The fix: Open the Paystack dashboard, go to Settings > API Keys & Webhooks, and visually confirm the key matches what your server is using. Compare the last 6 characters rather than trying to match the whole string.
// Add this to your server startup to make mismatches obvious
function validatePaystackKeyOrigin() {
const key = process.env.PAYSTACK_SECRET_KEY?.trim();
if (!key) {
throw new Error('PAYSTACK_SECRET_KEY not set');
}
const suffix = key.slice(-6);
console.log(`Paystack key ending in: ...${suffix}`);
console.log(`Key type: ${key.startsWith('sk_live_') ? 'LIVE' : 'TEST'}`);
// Compare this output against your Paystack dashboard
}
For teams, store keys in a secrets manager (AWS Secrets Manager, Doppler, Vault) with clear naming: paystack/projectname/secret_key. This prevents the "which account is this from?" confusion.
Cause 5: Key Was Regenerated (Old Key Expired)
When you click "Regenerate" on a secret key in the Paystack dashboard, the old key dies immediately. There is no grace period. Any server still using the old key will start returning "Invalid key" on every request.
This catches teams off guard when:
- Someone on the team regenerates keys without telling everyone
- You regenerate keys as a security precaution after a suspected leak
- You regenerate keys during a Paystack support interaction and forget to update all services
- Your staging server is still using a key that was regenerated weeks ago
The fix: After regenerating, update the key in every place it is used. This means environment variables on every deployment target, CI/CD secrets, and any secrets manager.
# Checklist after regenerating a Paystack key:
# 1. Update production environment variables
# Vercel:
vercel env rm PAYSTACK_SECRET_KEY production
vercel env add PAYSTACK_SECRET_KEY production
# Railway:
railway variables set PAYSTACK_SECRET_KEY=sk_live_new_key_here
# 2. Update staging/preview environments
# 3. Update CI/CD secrets (GitHub Actions, etc.)
# 4. Update any secrets manager entries
# 5. Redeploy all services that use the key
# 6. Verify with a test API call
To avoid downtime during key rotation, some teams use a wrapper that tries the new key first and falls back to the old one. But Paystack does not support overlapping validity periods for keys, so this only helps if you update different services sequentially.
Step-by-Step Diagnosis Flowchart
When you hit "Invalid key", run through this checklist in order. Each step takes under a minute.
Step 1: Check the prefix.
const key = process.env.PAYSTACK_SECRET_KEY || '';
console.log('Prefix:', key.substring(0, 8));
// Must be "sk_test_" or "sk_live_"
// If it starts with "pk_", that is your problem. Switch to the secret key.
Step 2: Check for whitespace.
console.log('Raw length:', key.length);
console.log('Trimmed length:', key.trim().length);
// If these differ, you have whitespace. Trim the value.
Step 3: Confirm the environment.
const isLive = key.startsWith('sk_live_');
console.log('Mode:', isLive ? 'LIVE' : 'TEST');
// Make sure this matches your intent
Step 4: Confirm the account.
Open the Paystack dashboard. Go to Settings > API Keys & Webhooks. Compare the last 6 characters of the key displayed there with the last 6 characters your server is using.
Step 5: Check if the key was regenerated.
If the key in your dashboard does not match the key your server has, someone regenerated it. Copy the new key and update your environment variables.
Step 6: Make a minimal test call.
curl -H "Authorization: Bearer sk_test_YOUR_KEY_HERE" \
https://api.paystack.co/transaction/verify/test_ref_123
# If you get { "status": false, "message": "Invalid key" }, the key itself is the problem
# If you get { "status": false, "message": "Transaction reference not found" },
# the key is working. The error is elsewhere.
Code Fix: A Safe Key Loader
Rather than debugging this every time it happens, build a key loader that catches these problems at startup. If the key is wrong, your server should refuse to start instead of serving 401s to your users.
// lib/paystack-key.ts
export function loadPaystackKeys() {
const secretKey = process.env.PAYSTACK_SECRET_KEY?.trim();
const publicKey = process.env.PAYSTACK_PUBLIC_KEY?.trim();
if (!secretKey) {
throw new Error(
'PAYSTACK_SECRET_KEY is not set. Add it to your environment variables.'
);
}
if (!secretKey.startsWith('sk_test_') && !secretKey.startsWith('sk_live_')) {
throw new Error(
`PAYSTACK_SECRET_KEY has wrong prefix: "${secretKey.substring(0, 8)}...". ` +
'It must start with "sk_test_" or "sk_live_". ' +
'You may have used a public key by mistake.'
);
}
if (publicKey && !publicKey.startsWith('pk_test_') && !publicKey.startsWith('pk_live_')) {
throw new Error(
`PAYSTACK_PUBLIC_KEY has wrong prefix: "${publicKey.substring(0, 8)}...". ` +
'It must start with "pk_test_" or "pk_live_".'
);
}
// Check environment consistency
if (publicKey) {
const secretIsLive = secretKey.startsWith('sk_live_');
const publicIsLive = publicKey.startsWith('pk_live_');
if (secretIsLive !== publicIsLive) {
throw new Error(
'Paystack key environment mismatch. ' +
`Secret key is ${secretIsLive ? 'LIVE' : 'TEST'} but ` +
`public key is ${publicIsLive ? 'LIVE' : 'TEST'}. ` +
'Both must be from the same environment.'
);
}
}
const mode = secretKey.startsWith('sk_live_') ? 'live' : 'test';
console.log(`Paystack keys loaded successfully (mode: ${mode})`);
return { secretKey, publicKey, mode };
}
// Usage in your server entry point:
// const { secretKey } = loadPaystackKeys();
// If any check fails, the server crashes with a clear error message
// before serving any requests.
Call loadPaystackKeys() at server startup. If anything is wrong, you get a descriptive error message in your logs instead of mysterious 401s in production.
Verification: Confirm Your Key Works
After fixing the key, verify it works before deploying. The lightest Paystack API call you can make is listing transactions with a limit of 1.
// Quick verification script
// Run: node verify-paystack-key.js
async function verifyKey() {
const key = process.env.PAYSTACK_SECRET_KEY?.trim();
if (!key) {
console.error('PAYSTACK_SECRET_KEY not set');
process.exit(1);
}
try {
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 === true) {
console.log('Key is valid and working');
console.log(`Mode: ${key.startsWith('sk_live_') ? 'LIVE' : 'TEST'}`);
console.log(`Transactions found: ${data.data.length}`);
} else {
console.error('Key rejected:', data.message);
process.exit(1);
}
} catch (err) {
console.error('Network error:', err);
process.exit(1);
}
}
verifyKey();
Run this script in every environment (local, staging, production) after updating keys. A one-line output confirms the key works. Any error message tells you exactly what is still wrong.
Preventing the Invalid Key Error
After you have been burned by this error once, here is how to make sure it never happens again:
- Use the key loader above. Validate keys at startup, not at request time. A server that refuses to start is better than a server that silently fails on every payment.
- Name your environment variables clearly. Use
PAYSTACK_SECRET_KEYandPAYSTACK_PUBLIC_KEY, notPAYSTACK_KEY(ambiguous) orAPI_KEY(which key?). - Never hardcode keys. They should live in environment variables, a secrets manager, or your deployment platform's secret store. Hardcoded keys get committed to Git and end up on GitHub for the world to see.
- Document key regeneration. If your team needs to rotate keys, write a runbook listing every service and environment that needs updating. Check the runbook after every rotation.
- Set up monitoring. Alert on 401 responses from Paystack. If your key breaks in production, you want to know within minutes, not when a customer complains.
For secure key storage patterns, see Storing Paystack Keys in Environment Variables. For understanding the full error landscape, see the Paystack Errors and Troubleshooting: The Complete Reference.
Key Takeaways
- ✓Paystack returns "Invalid key" when the Authorization header contains a key that does not match any active key on your account. The fix depends on which of five causes is responsible.
- ✓Public keys (pk_test_, pk_live_) are for the frontend checkout popup only. Server-side API calls require secret keys (sk_test_, sk_live_). Swapping them is the number one cause of this error.
- ✓Test keys only work in test mode and live keys only work in live mode. There is no cross-environment compatibility.
- ✓Whitespace and newline characters at the end of an API key are invisible in most editors but will cause Paystack to reject the key. Always trim your environment variables.
- ✓When you regenerate a key in the Paystack dashboard, the old key is immediately invalidated. Any server still using the old key will start getting "Invalid key" errors.
- ✓If you have multiple Paystack accounts (common when managing client projects), the key from Account A will not work on Account B. Verify which account the key belongs to.
- ✓A quick diagnostic: log the first 12 characters of the key your server is sending. That tells you the key type and environment without exposing the full secret.
Frequently Asked Questions
- What does the Paystack "Invalid key" error mean?
- It means the API key in your Authorization header does not match any active key on any Paystack account. The most common cause is using a public key (pk_) where a secret key (sk_) is required. Other causes include test/live mode mismatch, whitespace in the key, using a key from the wrong account, or using a key that was regenerated.
- Can I use a Paystack test key in live mode?
- No. Test keys (sk_test_) only work in the test environment. Live keys (sk_live_) only work in the live environment. Both environments use the same base URL, so the only difference is the key itself. Using a test key for live transactions will return "Invalid key".
- How do I know if my Paystack key was regenerated?
- Go to the Paystack dashboard, navigate to Settings > API Keys & Webhooks, and compare the key shown there with the key your server is using. If they do not match, someone regenerated the key. Copy the new key and update all your environment variables and secrets.
- Does Paystack support API key rotation without downtime?
- No. When you regenerate a key in the Paystack dashboard, the old key is immediately invalidated. There is no overlap period. To minimise downtime, update all services as quickly as possible after regeneration and verify each one with a test API call.
- How can I check my Paystack key without making a real transaction?
- Call any read-only endpoint like GET /transaction?perPage=1 with your key. If you get a 200 with "status": true, the key is valid. If you get a 401 with "Invalid key", it is not. This works for both test and live keys and does not create any transactions.
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