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. Building a Membership Site on Paystack
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Building a Membership Site on Paystack

Create a Paystack plan (monthly or annually) via the dashboard or API. Initialize a transaction with the plan code to subscribe a member. After subscription.create webhook, set the user as active in your database. Gate content with a middleware that checks membership status. Handle invoice.payment_failed to restrict access after failed renewal and subscription.disable to remove access on cancellation.

In this article

  1. 1. Plans and Subscription Flow
  2. 2. Webhook Handler and Content Gating
  3. 3. Learn More

Plans and Subscription Flow

// Create a plan via API (or do this once in the dashboard)
app.post('/api/admin/plans', async (req, res) => {
  var { name, amount, interval } = req.body;
  var response = await fetch('https://api.paystack.co/plan', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name, amount: amount * 100, interval }),
    // interval: 'monthly' | 'quarterly' | 'annually'
  });
  var data = await response.json();
  res.json({ plan_code: data.data.plan_code });
});

// Subscribe a member
app.post('/api/subscribe', async (req, res) => {
  var { email, planCode } = req.body;
  var reference = 'mem_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7);

  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      plan: planCode,
      reference,
      metadata: { action: 'subscribe', plan_code: planCode },
    }),
  });

  var data = await response.json();
  res.json({ authorization_url: data.data.authorization_url, reference });
});

Webhook Handler and Content Gating

// Webhook handler
if (event.event === 'subscription.create') {
  var sub = event.data;
  await db.users.update({
    membership_status: 'active',
    subscription_code: sub.subscription_code,
    email_token: sub.email_token,
    plan_code: sub.plan.plan_code,
    plan_name: sub.plan.name,
    membership_expires_at: sub.next_payment_date,
  }, { where: { email: sub.customer.email } });
}

if (event.event === 'charge.success' && event.data.plan) {
  await db.users.update({
    membership_status: 'active',
    membership_expires_at: event.data.plan.next_payment_date,
  }, { where: { email: event.data.customer.email } });
}

if (event.event === 'invoice.payment_failed') {
  await db.users.update(
    { membership_status: 'past_due' },
    { where: { email: event.data.customer.email } }
  );
  await sendCardUpdateEmail(event.data.customer.email);
}

if (event.event === 'subscription.disable') {
  await db.users.update(
    { membership_status: 'cancelled' },
    { where: { email: event.data.customer.email } }
  );
}

// Content gating middleware
function requireMembership(req, res, next) {
  var user = req.user;
  var accessAllowed = user.membership_status === 'active' ||
    (user.membership_status === 'past_due' && isWithinGracePeriod(user));

  if (!accessAllowed) {
    return res.status(403).json({ error: 'Membership required', upgrade_url: '/pricing' });
  }
  next();
}

function isWithinGracePeriod(user) {
  if (user.membership_status !== 'past_due') return false;
  var graceDays = 3;
  var graceEnd = new Date(user.membership_expires_at);
  graceEnd.setDate(graceEnd.getDate() + graceDays);
  return new Date() < graceEnd;
}

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

This guide is part of the Paystack recurring billing series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Create plans in the Paystack dashboard or via POST /plan. Tiers (basic, pro, enterprise) are separate plans with different amounts.
  • ✓Subscribe a user by initializing a transaction with their plan code. Paystack handles billing date calculation.
  • ✓On subscription.create webhook, set user.membership_status = "active" and store the subscription_code and email_token.
  • ✓Gate routes with a middleware that checks membership_status and membership_expires_at from your database.
  • ✓On invoice.payment_failed, show a card update prompt but do not immediately revoke access — give a 3-day grace period.
  • ✓On subscription.disable, set membership_status = "cancelled" and restrict access at the period end date.

Frequently Asked Questions

How do I offer annual billing at a discount compared to monthly?
Create two separate Paystack plans: one monthly and one annual. Set the annual plan amount to 10x the monthly (giving two months free, for example). Display both options on your pricing page. When the user selects annual, initialize the transaction with the annual plan code.
Can members upgrade or downgrade their plan?
Paystack does not have a native upgrade/downgrade endpoint. The standard approach: cancel the current subscription (POST /subscription/disable) and immediately start a new subscription with the new plan code. Handle proration manually if needed — credit unused days or charge a prorated top-up.
How do I handle a member who cancels mid-billing-period?
Paystack does not automatically prorate or refund the unused portion. Your product decision: either grant access until the paid period ends (recommended — simpler), or refund the unused portion manually via the Paystack refund API. Clearly state your cancellation policy in your terms of service.
What is the best way to let members manage their own subscription?
Build a member portal with three actions: (1) Update card — link to re-subscribe flow for new authorization, (2) Cancel — call your cancel endpoint which calls /subscription/disable, (3) Billing history — query your payments table. The Paystack customer portal (paystack.co/manage) is also an option for basic self-service.

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 Subscriptions and Recurring Billing: Complete Guide

Everything you need to build recurring billing on Paystack. Covers the subscriptions API, plans, authorization codes for manual charges, dunning, lifecycle webhooks, cancellation, and real-world patterns for SaaS, memberships, school fees, and loan collection across Africa.

Read more

Handling Failed Recurring Charges and Dunning on Paystack

Recurring charges fail. Cards expire, accounts run dry, banks decline for opaque reasons. This guide covers every failure scenario in Paystack recurring billing, shows you how to read gateway responses, build a retry strategy, implement a dunning state machine, and handle the higher failure rates typical in African card markets.

Read more

Reusable vs Non-Reusable Paystack Authorizations

Not every Paystack authorization can be charged again. Card payments produce reusable authorizations that work for recurring billing. Bank transfers and USSD produce non-reusable ones. This guide explains the difference, shows you how to check and handle each type, and covers the edge cases across different African payment channels.

Read more

Coupons and Discounts on Paystack Subscriptions

Paystack does not have a native coupon system. To offer discounts on subscriptions, you either create separate discounted plans or manage coupons in your application and charge discounted amounts via charge_authorization. This guide covers both approaches with code examples, coupon validation, and tracking.

Read more

Paystack Popup (InlineJS) Explained Line by Line

Paystack Inline JS lets you open a payment popup on your page instead of redirecting to Paystack. This guide breaks down V2 line by line: the script tag, PaystackPop constructor, every configuration option, onSuccess, onClose, onLoad callbacks, and the two checkout modes.

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.