Use the Subscriptions API when your billing has fixed amounts and standard intervals (monthly, quarterly, annually). Use manual charge_authorization when amounts vary, schedules are irregular, or you need control over retry logic. Many production systems use both. The Subscriptions API reduces code but limits flexibility. Manual charges give you full control but require you to build scheduling, retries, and lifecycle management yourself.
Paystack offers recurring billing through two distinct mechanisms. Understanding the tradeoffs between them is one of the most important architectural decisions you will make when building a billing system for African markets.
Path 1: The Subscriptions API
You create a Plan (price + interval), subscribe a Customer to it, and Paystack handles the rest. Paystack generates invoices, charges the customer's saved card on each billing date, retries failures, and sends webhook events at each step. You listen and react.
Path 2: Manual recurring charges (charge_authorization)
You store the customer's authorization code after their first payment, then call charge_authorization on your own schedule with your own amounts. You build the scheduling, the retry logic, and the lifecycle management. Paystack just executes each charge.
Both paths use the same underlying mechanism: authorization codes. The Subscriptions API calls charge_authorization internally. The difference is who manages the schedule and the logic.
This article is part of the subscriptions and recurring billing guide.
| Dimension | Subscriptions API | Manual charge_authorization |
|---|---|---|
| Scheduling | Paystack manages. Charges happen on the billing date automatically. | You manage. You decide when to charge via cron jobs, task queues, or application logic. |
| Amount flexibility | Fixed. The plan defines the amount. Cannot change per cycle. | Fully flexible. You set the amount on each call. |
| Interval options | Standard only: daily, weekly, monthly, quarterly, biannually, annually. | Any schedule you want. Every 10 days, twice a term, on specific calendar dates. |
| Retry logic | Paystack retries failed charges automatically. | You build retry logic yourself. |
| Proration | Not supported. You handle proration manually even with subscriptions. | You calculate and charge prorated amounts yourself. |
| Webhook events | Rich lifecycle events: subscription.create, invoice.create, invoice.payment_failed, subscription.disable. | Only charge.success and charge.failed. You build your own lifecycle events. |
| Customer self-service | email_token enables Paystack-hosted management page. | You build your own management UI. |
| Code complexity | Lower. Create plan, subscribe customer, handle webhooks. | Higher. Build scheduler, state machine, retry queue, dunning flow. |
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
The Subscriptions API is the right choice when your billing matches its constraints. If all of these are true, use it:
Code for the Subscriptions API path:
var axios = require('axios');
var headers = {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
};
// Step 1: Create a plan (do this once, store the plan_code)
async function createPlan() {
var response = await axios.post(
'https://api.paystack.co/plan',
{
name: 'Pro Monthly',
amount: 500000,
interval: 'monthly',
currency: 'NGN',
},
{ headers: headers }
);
return response.data.data.plan_code;
}
// Step 2: Subscribe customer during checkout
async function subscribeCustomer(email, planCode) {
var response = await axios.post(
'https://api.paystack.co/transaction/initialize',
{
email: email,
amount: 500000,
plan: planCode,
},
{ headers: headers }
);
return response.data.data.authorization_url;
}
// Step 3: Handle webhooks
function handleSubscriptionWebhook(event) {
if (event.event === 'subscription.create') {
activateUser(event.data.customer.email, event.data.subscription_code);
}
if (event.event === 'invoice.payment_failed') {
startDunning(event.data.customer.email);
}
if (event.event === 'subscription.disable') {
deactivateUser(event.data.customer.email);
}
}
Real-world examples that fit: SaaS products with fixed pricing tiers, news or media subscriptions, streaming services, and any product with a standard monthly or annual fee.
Use manual charges when your billing does not fit the Subscriptions API constraints. If any of these are true, go manual:
Code for the manual charge path:
var axios = require('axios');
var headers = {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
};
// Step 1: Collect first payment and store authorization
async function collectFirstPayment(email, amount) {
var response = await axios.post(
'https://api.paystack.co/transaction/initialize',
{
email: email,
amount: amount,
callback_url: 'https://yoursite.com/payment-callback',
},
{ headers: headers }
);
return response.data.data.authorization_url;
}
// Step 2: Store authorization from webhook
function handleChargeSuccess(eventData) {
var auth = eventData.authorization;
if (auth.reusable) {
saveAuthorization(eventData.customer.email, auth);
}
}
// Step 3: Charge on your own schedule
async function chargeCustomer(email, amount, authCode, reference) {
var response = await axios.post(
'https://api.paystack.co/transaction/charge_authorization',
{
email: email,
amount: amount,
authorization_code: authCode,
reference: reference,
},
{ headers: headers }
);
return response.data.data;
}
// Step 4: Run charges on a schedule (e.g., cron job)
async function runMonthlyBilling() {
var customers = await db.query(
'SELECT u.email, pm.authorization_code, b.amount_due FROM users u JOIN payment_methods pm ON u.id = pm.user_id JOIN billing b ON u.id = b.user_id WHERE b.next_charge_date <= NOW() AND pm.is_default = true'
);
for (var i = 0; i < customers.rows.length; i++) {
var c = customers.rows[i];
var ref = 'BILL_' + c.email + '_' + Date.now();
var result = await chargeCustomer(c.email, c.amount_due, c.authorization_code, ref);
if (result.status === 'success') {
await markBillingPaid(c.email, ref);
} else {
await scheduleDunning(c.email, result.gateway_response);
}
}
}
Real-world examples: school fee instalments, loan repayment collection, API usage billing, cloud infrastructure billing, and any product where the amount or schedule varies per customer.
Many production systems use both. This is not a compromise; it is often the best architecture.
Pattern: Base subscription + overage charges
Use the Subscriptions API for the base plan (fixed monthly fee). At the end of the month, calculate any overage (API calls beyond the included limit, storage beyond the quota) and charge it via charge_authorization.
// The Subscriptions API handles the base plan automatically.
// This function handles overages at the end of the billing period.
async function chargeOverage(userId) {
var usage = await calculateMonthlyUsage(userId);
var included = await getIncludedQuota(userId);
if (usage <= included) {
console.log('No overage for user ' + userId);
return;
}
var overageUnits = usage - included;
var overageAmount = overageUnits * PRICE_PER_UNIT;
var paymentMethod = await getDefaultPaymentMethod(userId);
var user = await getUser(userId);
var ref = 'OVERAGE_' + userId + '_' + Date.now();
var result = await chargeCustomer(
user.email,
overageAmount,
paymentMethod.authorization_code,
ref
);
if (result.status === 'success') {
await recordOveragePayment(userId, overageUnits, overageAmount, ref);
} else {
await handleOverageFailure(userId, overageAmount, result.gateway_response);
}
}
Pattern: Subscription with prorated upgrades
Base plan on the Subscriptions API. When the customer upgrades mid-cycle, calculate the prorated difference and charge it via charge_authorization. Cancel the old subscription and create a new one on the upgraded plan starting at the next billing date.
Pattern: Trial period managed manually, then subscription
Collect card details during signup with a minimal charge. Manage the trial period in your application. When the trial ends, create a Paystack subscription using the stored authorization. See free trials with Paystack subscriptions.
Choosing manual charge_authorization means you are building a billing engine. Here is what that requires:
1. A charge scheduler
A cron job, task queue (Bull, BullMQ), or cloud scheduler (AWS EventBridge, Google Cloud Scheduler) that triggers charges at the right time. This needs to be reliable. A missed cron run means missed revenue.
2. A state machine
Track each customer's billing state: trialing, active, past_due, cancelled, expired. Transition between states based on charge results and customer actions. See building your own billing engine.
3. Retry logic
When a charge fails, schedule retries with increasing delays. Track how many retries have been attempted. Stop after a maximum number. See handling failed charges and dunning.
4. Dunning communication
Email or SMS the customer when charges fail. Escalate the urgency with each retry. See building a dunning email sequence.
5. Invoice generation
Generate invoices or receipts for each charge. Store them in your database for accounting and customer access.
6. Reconciliation
Compare your charge records with Paystack's transaction records periodically. Catch any discrepancies (charges that succeeded on Paystack but your database missed, or vice versa).
This is real engineering work. Budget weeks or months for it, not days. The Subscriptions API exists to save you from building all of this for simple billing scenarios.
Use this flowchart to decide your approach:
Mapping common African business models:
You might start with the Subscriptions API and later realize you need manual charges (or vice versa). Here is how to migrate.
From Subscriptions API to manual charges:
From manual charges to Subscriptions API:
In both cases, the authorization code carries over. The customer does not need to re-enter card details. The migration is about which system manages the schedule, not about the payment method.
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