Build a subscription box backend using Paystack Plans for monthly billing. Subscribers sign up via the transaction initialize endpoint with a plan attached. Your backend receives subscription events, manages box assignments for each cycle, handles skips and pauses, and triggers shipping when a cycle payment succeeds.
Tables: plans (name, description, price, paystack_plan_code), subscribers (name, email, phone, address, plan_id, paystack_subscription_code, status, joined_at), box_cycles (month, year, cutoff_date), shipments (subscriber_id, cycle_id, box_contents, shipping_address, status, tracking_number).
// Create plans once (run during setup)
async function createSubscriptionPlan(name, priceInKobo, description) {
var res = 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: priceInKobo, interval: 'monthly', currency: 'NGN', description }),
});
return (await res.json()).data.plan_code;
}
// Subscriber sign-up
async function subscribeCustomer(customerEmail, planId) {
var plan = await db.plans.findById(planId);
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: customerEmail, amount: plan.price,
plan: plan.paystack_plan_code,
metadata: { plan_id: planId, purpose: 'subscription_signup' },
}),
});
return (await res.json()).data.authorization_url;
}
// Webhook: subscription created
if (event.event === 'subscription.create') {
var meta = event.data.metadata;
await db.subscribers.update({ email: event.data.customer.email }, {
paystack_subscription_code: event.data.subscription_code,
status: 'active',
});
}
// Each month's renewal triggers box preparation
if (event.event === 'invoice.create' && event.data.status === 'success') {
var subscriptionCode = event.data.subscription.subscription_code;
var subscriber = await db.subscribers.findByCode(subscriptionCode);
var currentCycle = await db.boxCycles.findCurrentMonth();
// Only prepare box if subscriber joined before cutoff
if (subscriber.joined_at <= currentCycle.cutoff_date) {
await db.shipments.create({
subscriber_id: subscriber.id,
cycle_id: currentCycle.id,
shipping_address: subscriber.address,
status: 'preparing',
});
await notifyWarehouse(subscriber.id, currentCycle.id);
}
}
// Handle failed payment — grace period
if (event.event === 'invoice.payment_failed') {
var code = event.data.subscription.subscription_code;
await db.subscribers.update({ subscription_code: code }, { status: 'payment_failed' });
await sendRetryPaymentEmail(code); // remind to update payment method
}
// Skip a month — subscriber requests via your website
async function skipMonth(subscriberId, skipReason) {
var subscriber = await db.subscribers.findById(subscriberId);
// Disable next renewal
await fetch('https://api.paystack.co/subscription/disable', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ code: subscriber.paystack_subscription_code, token: subscriber.email_token }),
});
await db.subscribers.update(subscriberId, { next_action: 'skip', skip_reason: skipReason });
}
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 water delivery subscription service for a recurring delivery model with similar billing architecture.
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