Storing Paystack Keys in Environment Variables Properly
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.
Why Environment Variables Matter for Payment Keys
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.
The .env File and How to Use It
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
Public vs Secret: Which Variable Goes Where
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.
Framework-Specific Patterns
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
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 (React, Vue, Svelte)
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.js / Plain Node.js
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 });
});
Django / Python
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 / PHP
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'),
],
Validate Keys at Startup
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.
Managing Keys Across Multiple Environments
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.
Common Mistakes to Avoid
These mistakes show up repeatedly in code reviews and incident reports:
- Committing .env before adding .gitignore. If .env is in even one commit, it is in the history forever. Always add .gitignore first.
- Using NEXT_PUBLIC_ prefix for the secret key. This bundles the secret key into your frontend JavaScript. Anyone can extract it from the browser.
- Logging process.env at startup for debugging. This dumps all environment variables, including secret keys, to your log service. Log individual non-sensitive variables if you must.
- Copying keys between environments manually. Use your hosting platform's environment variable UI or a secrets manager. Manual copying leads to live keys in staging or test keys in production.
- Storing keys in a config.json file that gets committed. JSON config files are not environment variables. They get committed to version control. Use .env instead.
- Not using different keys for staging and production. If staging accidentally uses live keys, a test purchase moves real money.
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.
When to Use a Secrets Manager
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:
- Vercel Environment Variables. Set them in the Vercel dashboard. They are encrypted at rest and injected at build time or runtime depending on the prefix.
- Railway Variables. Similar to Vercel. Set in the Railway dashboard, encrypted at rest, available at runtime.
- AWS Secrets Manager. Enterprise-grade. Supports automatic rotation, fine-grained IAM access control, and cross-region replication.
- HashiCorp Vault. Self-hosted option. Good for teams that need on-premise secrets management or run across multiple cloud providers.
- Doppler. Developer-friendly SaaS secrets manager. Syncs secrets across environments and integrates with most deployment platforms.
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.
Key Takeaways
- ✓Environment variables keep Paystack keys out of source code and version control, reducing the risk of accidental leaks.
- ✓The secret key (sk_live_* or sk_test_*) belongs in a server-only variable like PAYSTACK_SECRET_KEY. It must never appear in frontend bundles.
- ✓The public key (pk_live_* or pk_test_*) can go in a client-safe variable like NEXT_PUBLIC_PAYSTACK_KEY or VITE_PAYSTACK_KEY, because public keys can only initialize checkouts.
- ✓Always add .env to your .gitignore before your first commit. Create a .env.example file with placeholder values so teammates know which variables to set.
- ✓Validate that required environment variables exist at startup. A missing PAYSTACK_SECRET_KEY should crash the server immediately, not fail silently on the first payment.
- ✓Different frameworks have different rules for which environment variables reach the browser. Next.js requires NEXT_PUBLIC_ prefix, Vite requires VITE_ prefix. Know your framework.
Frequently Asked Questions
- Can I store Paystack keys in a database instead of environment variables?
- You can, but it adds complexity without clear benefit for most projects. You need to secure the database connection string (which itself is an environment variable), handle encryption at rest, and ensure the key is not logged in database query logs. Environment variables are simpler and more standard.
- What happens if I accidentally use live keys in my test environment?
- Any transaction you initialize will process real money. A test purchase with a real card will charge the customer. Always validate the key prefix at startup to catch this mistake before it costs money.
- Should I use the same PAYSTACK_SECRET_KEY variable name across all environments?
- Yes. Use the same variable name and swap the value. Your code reads PAYSTACK_SECRET_KEY regardless of the environment. The development .env file contains a test key. The production platform contains the live key. The code does not change.
- Is it safe to put the public key in my .env file?
- Yes. The public key is designed to be visible in the browser. Putting it in .env just keeps your code clean and makes it easy to swap between test and live keys. The key itself is not a secret.
- How do I share environment variables with my team?
- Never share .env files through chat, email, or version control. Use your hosting platform environment variable settings, a secrets manager like Doppler, or a password manager with shared vaults. Create a .env.example file with placeholder values so teammates know which variables to set.
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