Store your Paystack secret key in a server-side environment variable like PAYSTACK_SECRET_KEY. Store the public key in a client-safe variable like NEXT_PUBLIC_PAYSTACK_KEY. Never commit .env files to version control. Use .env.example with placeholder values for documentation, and always verify that your build tool does not bundle secret variables into client-side JavaScript.
Hardcoding an API key in a source file is the fastest way to leak it. Once that file is pushed to a repository, the key lives in the commit history forever. Even if you delete the line and push again, anyone with access to the repo can find it in the git log.
Environment variables solve this by separating configuration from code. The key lives on the server (or in a platform's settings panel), not in any file that gets committed. Your code reads the value at runtime. If the key needs to change, you update the variable without touching the codebase.
For Paystack specifically, you are dealing with keys that can authorize real money transfers. A leaked sk_live_ key means an attacker can verify transactions, initiate transfers out of your account, and read all your customer data. The stakes are high enough that getting environment variable management right is not optional.
Most Node.js projects use a .env file at the project root, loaded by the dotenv package. Here is what a properly structured .env file looks like for a Paystack integration:
# .env (never commit this file)
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx
PAYSTACK_PUBLIC_KEY=pk_test_xxxxxxxxxxxxxxxxxxxx
PAYSTACK_WEBHOOK_SECRET=whsk_xxxxxxxxxxxxxxxxxxxx
And the companion file that does get committed:
# .env.example (commit this)
PAYSTACK_SECRET_KEY=sk_test_your_key_here
PAYSTACK_PUBLIC_KEY=pk_test_your_key_here
PAYSTACK_WEBHOOK_SECRET=whsk_your_webhook_secret_here
Load the variables at the start of your application:
// server.js
require('dotenv').config();
// Validate immediately
if (!process.env.PAYSTACK_SECRET_KEY) {
console.error('PAYSTACK_SECRET_KEY is not set. Exiting.');
process.exit(1);
}
// Now use it
var secretKey = process.env.PAYSTACK_SECRET_KEY;
The critical step most developers skip: add .env to your .gitignore before your very first commit. If you add it later, the file is already in the history.
# .gitignore
.env
.env.local
.env.production
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Paystack gives you two types of keys, and they have very different security profiles:
| Key Type | Variable Name | Where It Lives | Risk If Exposed |
|---|---|---|---|
| Public Key (pk_*) | PAYSTACK_PUBLIC_KEY | Frontend OK | Low. Can only open checkout popup. |
| Secret Key (sk_*) | PAYSTACK_SECRET_KEY | Server only | Critical. Full API access. |
The public key is designed to be visible in the browser. It is embedded in your JavaScript bundle and appears in the network tab of anyone visiting your site. This is expected. The key can only initialize a checkout. It cannot verify transactions, issue refunds, or move money.
The secret key is the opposite. If it appears anywhere a user can see it (browser console, frontend bundle, public repo, error log), you have a security incident. Treat it like a database password.
A common mistake: storing both keys in the same environment variable pattern and accidentally exposing the secret key through a frontend build tool. The next sections cover how each framework handles this differently.
Every framework has its own rules about which environment variables make it into the browser bundle. Getting this wrong is one of the most common security mistakes in Paystack integrations.
Next.js only exposes variables prefixed with NEXT_PUBLIC_ to the browser. Server-side variables stay on the server.
# .env.local
PAYSTACK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_PAYSTACK_KEY=pk_live_xxxxxxxxxxxxxxxxxxxx
In your frontend component, use process.env.NEXT_PUBLIC_PAYSTACK_KEY. In your API route, use process.env.PAYSTACK_SECRET_KEY. If you accidentally name the secret key NEXT_PUBLIC_PAYSTACK_SECRET_KEY, it will be bundled into your frontend JavaScript. Do not do this.
Vite exposes variables prefixed with VITE_ to the browser.
# .env
PAYSTACK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
VITE_PAYSTACK_KEY=pk_live_xxxxxxxxxxxxxxxxxxxx
Access the public key with import.meta.env.VITE_PAYSTACK_KEY. The secret key without the VITE_ prefix stays out of the bundle.
Express has no special variable prefix rules. Everything in process.env is server-side. Just make sure you never send environment variables to the client in an API response.
// WRONG: leaks the secret key to the client
app.get('/config', function(req, res) {
res.json({ key: process.env.PAYSTACK_SECRET_KEY });
});
// RIGHT: only send the public key
app.get('/config', function(req, res) {
res.json({ key: process.env.PAYSTACK_PUBLIC_KEY });
});
Use python-decouple or django-environ to load from a .env file:
# settings.py
from decouple import config
PAYSTACK_SECRET_KEY = config('PAYSTACK_SECRET_KEY')
PAYSTACK_PUBLIC_KEY = config('PAYSTACK_PUBLIC_KEY')
Laravel reads from .env automatically. Access keys with the env() helper:
// config/services.php
'paystack' => [
'secret' => env('PAYSTACK_SECRET_KEY'),
'public' => env('PAYSTACK_PUBLIC_KEY'),
],
A missing environment variable should not cause a silent failure on the first payment attempt ten minutes after deployment. It should crash the server immediately at startup, with a clear error message.
// config.js
var requiredVars = [
'PAYSTACK_SECRET_KEY',
'PAYSTACK_PUBLIC_KEY',
];
var missing = requiredVars.filter(function(name) {
return !process.env[name];
});
if (missing.length > 0) {
console.error('Missing required environment variables: ' + missing.join(', '));
process.exit(1);
}
// Optional: validate key format
var secretKey = process.env.PAYSTACK_SECRET_KEY;
if (!secretKey.startsWith('sk_test_') && !secretKey.startsWith('sk_live_')) {
console.error('PAYSTACK_SECRET_KEY does not look like a valid Paystack secret key');
process.exit(1);
}
module.exports = {
paystackSecretKey: secretKey,
paystackPublicKey: process.env.PAYSTACK_PUBLIC_KEY,
};
This pattern catches three common problems: forgetting to set a variable after deployment, copy-pasting the wrong key type, and accidentally setting a public key where a secret key should be.
Most projects have at least three environments: development (local machine), staging (preview deployment), and production (live). Each should use different Paystack keys.
| Environment | Key Type | Purpose |
|---|---|---|
| Development | sk_test_* / pk_test_* | Local coding and debugging |
| Staging | sk_test_* / pk_test_* | Pre-production verification |
| Production | sk_live_* / pk_live_* | Real payments |
Use separate .env files for each environment:
.env.development # local dev, test keys
.env.staging # staging server, test keys (different account)
.env.production # production server, live keys
Some teams use the same Paystack test account for development and staging. This works but creates noise. When two developers and the staging server all share test keys, the test dashboard fills with transactions from three sources. Consider creating a separate Paystack business for staging. See staging environments and separate Paystack accounts for details.
These mistakes show up repeatedly in code reviews and incident reports:
For a deeper look at what happens when keys do leak and the remediation steps, see what to do if your Paystack secret key leaked.
Environment variables loaded from .env files work for most projects. But as your team grows, you may want a secrets manager that provides audit logs, automatic rotation, and access control.
Options that work well in the African developer ecosystem:
For most Paystack integrations built by small to medium teams in Africa, the hosting platform's built-in environment variable management (Vercel, Railway, Render) is sufficient. Move to a dedicated secrets manager when you need audit trails, programmatic rotation, or multi-service secret sharing.
For platform-specific setup instructions, see Paystack keys in Vercel, Railway and Render.
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