McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Custom Paystack Plugin Development: A Primer
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Custom Paystack Plugin Development: A Primer

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.

In this article

  1. 1. Plugin Architecture
  2. 2. Registering with Your Platform
  3. 3. Learn More

Plugin Architecture

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 });
  }
}

Registering with Your Platform

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.

Learn More

See the full Paystack plugins overview for all integration options.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓A Paystack plugin wraps initialize, verify, and webhook handling into a reusable class.
  • ✓HMAC SHA-512 webhook signature validation must be in the plugin, not left to the implementer.
  • ✓Pass a fulfillment callback into the plugin so the business logic stays outside the payment layer.
  • ✓Idempotency key your verify calls — the webhook and the redirect can both trigger verification.
  • ✓Export your plugin with a configuration object: secretKey, webhookSecret, currency, and optional hooks.

Frequently Asked Questions

Should the plugin store the secret key or expect it from the environment?
Accept it as a constructor parameter, not a hardcoded value. This keeps the plugin flexible — the consuming app passes process.env.PAYSTACK_SECRET_KEY. Never hardcode credentials in the plugin source.
How do I handle the case where both the redirect and the webhook fire for the same payment?
Use idempotency: keep a Set of fulfilled references in memory, or better, use your database. Before calling onSuccess, check if the reference already has status "paid" in your DB. If yes, skip fulfillment and return early.
How should I package the plugin for distribution?
Export the class from an index.js, add a peerDependency for node-fetch or use globalThis.fetch (Node 18+), and document the constructor config object. For npm: set "main" to index.js, "types" to index.d.ts if TypeScript. For internal use, a local package in your monorepo packages/ folder is sufficient.

Learn Paystack Integration

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

Not ready? Create Free Account

Keep reading

Paystack Plugins and No-Code Integrations

A complete map of every official and community Paystack plugin, no-code connector, and automation tool. Covers e-commerce platforms, website builders, headless commerce, automation workflows, reporting integrations, and the decision framework for choosing plugins over custom code.

Read more

Paystack with OpenCart

A practical guide to integrating Paystack with OpenCart for African e-commerce stores. Covers extension installation, configuration, webhook setup, testing, and the most common setup problems.

Read more

Paystack with Zapier

Zapier connects Paystack to 6,000+ apps without writing code. This guide shows how to set up Paystack webhook triggers in Zapier and build the most common payment automation workflows for African businesses.

Read more

Paystack Storefront for a Small Business

Paystack Storefront gives small businesses a free online shop without needing a website or developer. List your products, set prices, and share a link. Customers browse, pick, and pay — you get notified and fulfill the order.

Read more

How Paystack Accept Payments Actually Works Under the Hood

Most tutorials show you the API call and skip to "payment successful." This guide traces what actually happens between your server, Paystack, the card networks, the issuing bank, and the acquiring bank. Every step, every handshake, every place things can fail.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.