Build a podcast membership platform by creating a Paystack Plan for your paid tier, subscribing listeners via the transaction initialize endpoint with the plan code, gating member-only episode access on active subscription status in your database, and listening to subscription.create, invoice.payment_failed, and subscription.disable webhooks to grant or revoke access automatically.
Tables: membership_plans (name, price, paystack_plan_code, features[]), members (user_id, plan_id, paystack_subscription_code, status, current_period_end), episodes (title, audio_url, is_members_only, published_at).
var PLANS = {
free: { tier: 0, label: 'Free — public episodes only' },
supporter: { tier: 1, price: 100000, label: 'Supporter — all episodes + bonus content' }, // NGN 1,000/mo
};
async function subscribeToPlan(userId, userEmail) {
var plan = await db.membershipPlans.findByName('supporter');
var res = 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: userEmail,
amount: plan.price,
plan: plan.paystack_plan_code,
metadata: { user_id: userId, plan_id: plan.id },
}),
});
return (await res.json()).data.authorization_url;
}
// Content access gate
async function canAccessEpisode(userId, episodeId) {
var episode = await db.episodes.findById(episodeId);
if (!episode.is_members_only) return true;
var member = await db.members.findActiveByUser(userId);
return !!(member && member.status === 'active');
}
async function getEpisodeAudio(userId, episodeId) {
var canAccess = await canAccessEpisode(userId, episodeId);
if (!canAccess) throw { status: 403, message: 'Subscribe to access this episode.' };
var episode = await db.episodes.findById(episodeId);
return episode.audio_url;
}
if (event.event === 'subscription.create') {
var meta = event.data.metadata;
await db.members.upsert({
user_id: meta.user_id,
plan_id: meta.plan_id,
paystack_subscription_code: event.data.subscription_code,
status: 'active',
current_period_end: new Date(event.data.next_payment_date),
});
await sendWelcomeEmail(meta.user_id);
}
if (event.event === 'invoice.create' && event.data.status === 'success') {
var code = event.data.subscription.subscription_code;
await db.members.update({ code }, {
status: 'active',
current_period_end: new Date(event.data.next_payment_date),
});
}
if (event.event === 'invoice.payment_failed') {
var code = event.data.subscription.subscription_code;
await db.members.update({ code }, { status: 'grace_period' });
await sendPaymentFailedEmail(code); // ask them to update payment method
}
if (event.event === 'subscription.disable') {
var code = event.data.subscription_code;
// Keep access until current_period_end; cron downgrades after
await db.members.update({ code }, { status: 'cancelled' });
}
// Cron: revoke access when period ends for cancelled/grace members
async function revokeExpiredAccess() {
var expired = await db.members.findExpiredBefore(new Date());
for (var member of expired) {
await db.members.update(member.id, { status: 'expired' });
await sendRenewalPromptEmail(member.user_id);
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
See build a streaming service billing backend for the multi-tier version of this membership model applied to video content.
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