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. Build Paystack Subscriptions in Node.js and Express
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Build Paystack Subscriptions in Node.js and Express

To build Paystack subscriptions in Express, first create a plan via the Paystack API with an interval (monthly, yearly, etc.) and amount. Then initialize a transaction with the plan code. After the first payment, Paystack automatically charges the customer on the plan interval. Handle subscription.create and invoice.payment_failed webhooks to track status.

In this article

  1. 1. How Paystack Subscriptions Work
  2. 2. Create a Plan
  3. 3. Subscribe a Customer
  4. 4. Handle Subscription Webhooks
  5. 5. Cancel and Manage Subscriptions
  6. 6. Check Subscription Status
  7. 7. Ship Payments Faster

How Paystack Subscriptions Work

Paystack subscriptions have three parts:

  1. Plans. A plan defines the amount, currency, billing interval (daily, weekly, monthly, quarterly, biannually, annually), and optional invoice limit.
  2. Subscriptions. When a customer pays for a plan, Paystack creates a subscription linking the customer to the plan. Paystack stores the customer's payment authorization and uses it for future charges.
  3. Invoices. On each billing cycle, Paystack creates an invoice and attempts to charge the customer. If it succeeds, you get a charge.success webhook. If it fails, you get invoice.payment_failed.

You do not need to build a cron job or scheduler. Paystack handles all the timing and retry logic for recurring charges.

Create a Plan

Create a plan via the API or the dashboard. Via the API:

var PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;

app.post('/api/plans', async function(req, res) {
  var name = req.body.name;
  var amount = req.body.amount;
  var interval = req.body.interval; // monthly, yearly, weekly, etc.

  var paystackRes = await fetch('https://api.paystack.co/plan', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + PAYSTACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: name,
      amount: Math.round(amount * 100),
      interval: interval,
      currency: 'NGN',
    }),
  });

  var data = await paystackRes.json();

  if (!data.status) {
    return res.status(400).json({ error: data.message });
  }

  // Save the plan_code to your database
  var planCode = data.data.plan_code;

  res.json({
    plan_code: planCode,
    name: data.data.name,
    amount: data.data.amount / 100,
    interval: data.data.interval,
  });
});

You only create each plan once. Store the plan_code in your database and reuse it for all customers subscribing to that plan. Most teams create plans through the dashboard and hardcode the plan codes in their application config.

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Subscribe a Customer

Initialize a transaction with the plan code. The first payment creates the subscription:

app.post('/api/subscribe', async function(req, res) {
  var email = req.body.email;
  var planCode = req.body.plan_code;

  var reference = 'sub_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);

  var paystackRes = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + PAYSTACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: email,
      plan: planCode,
      reference: reference,
      callback_url: process.env.BASE_URL + '/subscription/callback',
    }),
  });

  var data = await paystackRes.json();

  if (!data.status) {
    return res.status(400).json({ error: data.message });
  }

  // Save reference and plan to your database
  res.json({
    authorization_url: data.data.authorization_url,
    reference: data.data.reference,
  });
});

When you pass a plan parameter, you do not need to pass amount. Paystack uses the plan's amount. After the customer pays, Paystack creates the subscription and stores the payment authorization for future charges.

Handle Subscription Webhooks

Subscription management depends heavily on webhooks. Here are the events you need to handle:

async function handlePaystackEvent(event) {
  var eventType = event.event;
  var data = event.data;

  if (eventType === 'subscription.create') {
    // New subscription created
    await db.subscriptions.insertOne({
      subscriptionCode: data.subscription_code,
      customerEmail: data.customer.email,
      customerCode: data.customer.customer_code,
      planCode: data.plan.plan_code,
      status: 'active',
      nextPaymentDate: data.next_payment_date,
      createdAt: new Date(),
    });

  } else if (eventType === 'charge.success' && data.plan) {
    // Subscription renewal payment succeeded
    await db.subscriptions.updateOne(
      { subscriptionCode: data.subscription_code },
      { $set: { status: 'active', lastPaymentDate: new Date() } }
    );

  } else if (eventType === 'invoice.payment_failed') {
    // Subscription renewal failed
    await db.subscriptions.updateOne(
      { subscriptionCode: data.subscription.subscription_code },
      { $set: { status: 'past_due' } }
    );
    // Notify the customer to update their payment method

  } else if (eventType === 'subscription.disable') {
    // Subscription cancelled
    await db.subscriptions.updateOne(
      { subscriptionCode: data.subscription_code },
      { $set: { status: 'cancelled', cancelledAt: new Date() } }
    );
  }
}

Track the subscription status in your database. Use it to gate access to premium features. When a renewal fails, give the customer a grace period to update their payment method before revoking access.

Cancel and Manage Subscriptions

Cancel a subscription via the API:

app.post('/api/subscriptions/cancel', async function(req, res) {
  var subscriptionCode = req.body.subscription_code;
  var emailToken = req.body.email_token;

  var paystackRes = await fetch('https://api.paystack.co/subscription/disable', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + PAYSTACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      code: subscriptionCode,
      token: emailToken,
    }),
  });

  var data = await paystackRes.json();

  if (!data.status) {
    return res.status(400).json({ error: data.message });
  }

  res.json({ message: 'Subscription cancelled' });
});

The email_token is sent in the subscription.create webhook event. Store it alongside the subscription code. You need it for cancellation.

Alternatively, Paystack provides a cancellation link for each subscription. You can redirect customers to that link instead of building your own cancellation flow.

Check Subscription Status

Fetch the current status of a subscription from Paystack:

app.get('/api/subscriptions/:code', async function(req, res) {
  var code = req.params.code;

  var paystackRes = await fetch(
    'https://api.paystack.co/subscription/' + encodeURIComponent(code),
    { headers: { Authorization: 'Bearer ' + PAYSTACK_SECRET } }
  );

  var data = await paystackRes.json();

  if (!data.status) {
    return res.status(400).json({ error: data.message });
  }

  res.json({
    status: data.data.status,
    plan: data.data.plan.name,
    next_payment_date: data.data.next_payment_date,
    email_token: data.data.email_token,
  });
});

Use this to display the customer's subscription details in your app. For frequent access checks, rely on the status stored in your database (updated by webhooks) rather than calling Paystack on every page load.

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series. For webhook setup, see Handle Paystack Webhooks in Node.js and Express.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Paystack handles all the recurring billing logic. You create a plan, subscribe a customer, and Paystack charges them automatically on the interval.
  • ✓Plans are created once and reused. Each plan has a plan_code you pass when initializing a subscription transaction.
  • ✓The first payment creates the subscription. Subsequent charges happen automatically without customer interaction.
  • ✓Webhook events are critical for subscriptions. Handle subscription.create, charge.success, invoice.payment_failed, and subscription.disable.
  • ✓Customers can cancel via a link Paystack provides, or you can cancel programmatically via the API.
  • ✓Store the subscription_code and customer_code in your database. You need them to manage, pause, or cancel subscriptions.

Frequently Asked Questions

Can I change the price of a plan after customers have subscribed?
Updating a plan amount only affects new subscribers. Existing subscribers continue paying the original amount. To change the price for existing subscribers, create a new plan, cancel their current subscription, and subscribe them to the new plan.
What happens when a subscription payment fails?
Paystack retries the charge according to its retry schedule. You receive an invoice.payment_failed webhook for each failed attempt. After exhausting retries, the subscription status changes. Handle this by notifying the customer and providing a way to update their payment method.
Can customers upgrade or downgrade their plan?
Paystack does not have a built-in plan-change feature. To switch plans, cancel the current subscription and create a new one on the desired plan. Handle prorating on your side if needed.
How do I offer a free trial before charging?
Paystack does not have a native free trial feature. Implement trials in your application: grant access for the trial period, then initialize the subscription transaction when the trial ends. Collect payment details at trial start if you want a seamless transition.

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 Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in Svelte and SvelteKit

Accept Paystack payments in SvelteKit using server endpoints for backend logic and Svelte components for the frontend. This guide covers initializing transactions, inline popup checkout, redirect checkout, and verification in a single full-stack SvelteKit project.

Read more

Handle Paystack Webhooks in Flask

Receive and verify Paystack webhooks in your Flask application. This guide covers accessing request.data for raw bytes, HMAC SHA-512 signature verification, processing charge.success events, and making handlers idempotent.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Restricting Payment Channels at Checkout in Paystack

By default, Paystack checkout shows all available payment channels. But there are good reasons to restrict which channels appear: instant confirmation requirements, amount limits, or simpler support. This guide shows you how to use the channels parameter and the decision logic behind when to restrict.

Read more

Paystack Invalid Amount Error and Kobo Confusion

Paystack rejects amounts that are not positive integers in the smallest currency unit. This guide covers every way the amount field goes wrong: decimals, strings, Naira-vs-kobo confusion, and floating-point precision traps. Includes a safe conversion function you can drop into any codebase.

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.