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.
Paystack subscriptions have three parts:
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 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.
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.
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 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.
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.
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