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.
// 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
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.
This guide is part of the Paystack recurring billing series.
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