A custom Paystack plugin wraps the Paystack API into a reusable class: initialize(amount, email, metadata) calls POST /transaction/initialize, verifyAndFulfill(reference) calls GET /transaction/verify and runs your fulfillment logic, and handleWebhook(rawBody, signature) validates HMAC and dispatches events. Register these with your platform's payment hook system and export them as a package.
A Paystack plugin has three concerns: initialization (get a checkout URL), verification (confirm a payment), and webhook handling (receive server-to-server events). Keep these separate so the platform can use whichever flow it supports.
class PaystackPlugin {
constructor({ secretKey, webhookSecret, currency = 'NGN', onSuccess }) {
this.secretKey = secretKey;
this.webhookSecret = webhookSecret;
this.currency = currency;
this.onSuccess = onSuccess; // async (transaction) => {}
this._processed = new Set(); // idempotency
}
async initialize(email, amountMinorUnit, reference, metadata = {}) {
var res = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: { Authorization: 'Bearer ' + this.secretKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ email, amount: amountMinorUnit, reference, currency: this.currency, metadata }),
});
var data = await res.json();
if (!data.status) throw new Error(data.message);
return data.data.authorization_url;
}
async verifyAndFulfill(reference) {
if (this._processed.has(reference)) return { already: true };
var res = await fetch('https://api.paystack.co/transaction/verify/' + reference, {
headers: { Authorization: 'Bearer ' + this.secretKey },
});
var data = await res.json();
if (data.data.status !== 'success') throw new Error('Payment not successful');
this._processed.add(reference);
await this.onSuccess(data.data);
return data.data;
}
handleWebhook(rawBody, signature) {
var crypto = require('crypto');
var expected = crypto.createHmac('sha512', this.webhookSecret).update(rawBody).digest('hex');
if (expected !== signature) throw new Error('Invalid webhook signature');
var event = JSON.parse(rawBody);
if (event.event === 'charge.success') return this.verifyAndFulfill(event.data.reference);
return Promise.resolve({ ignored: true });
}
}
Most frameworks have a payment hook system. Register the plugin once at startup and let the platform call the methods:
// Express app example
var plugin = new PaystackPlugin({
secretKey: process.env.PAYSTACK_SECRET_KEY,
webhookSecret: process.env.PAYSTACK_WEBHOOK_SECRET,
currency: 'KES',
onSuccess: async (txn) => {
await db.orders.update({ reference: txn.reference }, { status: 'paid', paidAt: new Date() });
},
});
app.post('/checkout/start', async (req, res) => {
var url = await plugin.initialize(req.body.email, req.body.amount, req.body.reference);
res.json({ url });
});
app.get('/checkout/callback', async (req, res) => {
await plugin.verifyAndFulfill(req.query.reference);
res.redirect('/order-confirmed');
});
app.post('/webhooks/paystack', express.raw({ type: 'application/json' }), (req, res) => {
plugin.handleWebhook(req.body, req.headers['x-paystack-signature'])
.then(() => res.sendStatus(200))
.catch(() => res.sendStatus(400));
});
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
See the full Paystack plugins overview for all integration options.
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