Paystack recurring billing works two ways. The Subscriptions API manages plans, billing cycles, and renewals automatically. Authorization codes let you charge a saved card on your own schedule. Most African products need a mix of both. This guide covers the data model, the code, and the decisions you will face building real billing systems.
Paystack subscriptions automate recurring billing. You create a plan (the price and interval), subscribe a customer to it, and Paystack handles the rest: generating invoices, attempting charges on the billing date, sending receipts, and retrying failures.
The flow looks like this:
This is the managed path. Paystack does the scheduling, the charging, and the retry logic. You listen to webhooks and update your application state.
But there is a second path: manual recurring charges. Instead of using the Subscriptions API, you store the customer's authorization code after their first payment and call the Charge Authorization endpoint yourself whenever you want to bill them. You control the schedule, the amount, and the retry logic. Paystack just executes the charge.
Which path should you use? If your billing is straightforward (fixed amount, fixed interval, same day every month), use the Subscriptions API. If your billing is complex (variable amounts, irregular schedules, usage-based, instalment plans with changing balances), use authorization codes directly. Many production systems use both. For a detailed comparison, see subscriptions API vs manual recurring charges.
This guide is part of the complete Paystack engineering guide.
Three objects power Paystack recurring billing. Understanding how they relate saves you hours of confusion.
Plan
A plan is a billing template. It has a name, an amount (in the smallest currency unit), an interval, and a currency. Plans are reusable. You create one "Pro Monthly" plan and subscribe hundreds of customers to it. Plans do not belong to customers. They belong to your Paystack account.
{
"name": "Pro Monthly",
"amount": 500000,
"interval": "monthly",
"currency": "NGN",
"plan_code": "PLN_xxxxxxxxxxxxx"
}
The amount is 500000 kobo, which is 5,000 Naira. Paystack always uses the smallest currency unit. For KES, 500000 would be 5,000 KES (since KES does not have a widely used subunit in Paystack's system, but always confirm with the amount handling guide).
Subscription
A subscription links a customer to a plan. It tracks the billing cycle: when the next charge will happen, how many charges have been made, whether the subscription is active or cancelled. Each subscription has a subscription_code and an email_token the customer can use to manage it.
{
"subscription_code": "SUB_xxxxxxxxxxxxx",
"email_token": "abc123token",
"plan": { "plan_code": "PLN_xxxxxxxxxxxxx" },
"customer": { "customer_code": "CUS_xxxxxxxxxxxxx" },
"authorization": { "authorization_code": "AUTH_xxxxxxxxxxxxx" },
"status": "active",
"next_payment_date": "2026-08-20T00:00:00.000Z"
}
Authorization
An authorization represents a saved payment method. When a customer pays for the first time, Paystack tokenizes their card and returns an authorization code. This code is what Paystack uses to charge the card again without the customer re-entering details. Authorizations belong to the customer, not the subscription. A single customer can have multiple authorizations (different cards), and you can choose which one to use.
{
"authorization_code": "AUTH_xxxxxxxxxxxxx",
"bin": "408408",
"last4": "4081",
"exp_month": "12",
"exp_year": "2030",
"channel": "card",
"card_type": "visa",
"bank": "TEST BANK",
"reusable": true
}
The reusable field is critical. Only authorizations marked true can be used for recurring charges. Some payment channels (bank transfers, USSD) produce non-reusable authorizations. For a deep dive into this distinction, see reusable vs non-reusable authorizations.
For the full relationship diagram and edge cases, see the full data model article.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
You can create plans through the Paystack Dashboard or the API. For anything beyond a simple prototype, use the API so your plan definitions live in code and can be version-controlled.
const axios = require('axios');
async function createPlan() {
const response = await axios.post(
'https://api.paystack.co/plan',
{
name: 'Pro Monthly',
amount: 500000, // 5,000 NGN in kobo
interval: 'monthly', // daily, weekly, monthly, quarterly, annually
currency: 'NGN',
description: 'Pro tier with all features',
},
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
const plan = response.data.data;
console.log('Plan created:', plan.plan_code);
// Store plan.plan_code in your database or config
return plan;
}
createPlan();
Practical notes on plans:
invoice_limit to cap the number of charges. After that many renewals, the subscription automatically completes. Useful for fixed-term contracts like a 12-month commitment.For SaaS products with multiple tiers and add-ons, see building a SaaS billing page on Paystack.
There are two ways to subscribe a customer to a plan.
Method 1: Pass the plan code during the first checkout
When you initialize a transaction, include the plan parameter. After the customer pays, Paystack automatically creates a subscription linking them to that plan.
const express = require('express');
const axios = require('axios');
const router = express.Router();
router.post('/subscribe', async (req, res) => {
const { email } = req.body;
const response = await axios.post(
'https://api.paystack.co/transaction/initialize',
{
email,
amount: 500000, // Must match the plan amount
plan: 'PLN_xxxxxxxxxxxxx', // Your plan code
callback_url: 'https://yoursite.com/payment/callback',
},
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
// Redirect the user to Paystack checkout
res.json({ checkout_url: response.data.data.authorization_url });
});
When the customer completes checkout, Paystack creates both the authorization (saved card) and the subscription in one step. You will receive a charge.success webhook followed by a subscription.create webhook.
Method 2: Subscribe using an existing authorization
If the customer has already paid before and you have their authorization code stored, you can create a subscription without them going through checkout again.
async function subscribeExistingCustomer(customerEmail, authorizationCode) {
const response = await axios.post(
'https://api.paystack.co/subscription',
{
customer: customerEmail, // or customer_code
plan: 'PLN_xxxxxxxxxxxxx',
authorization: authorizationCode,
},
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
const subscription = response.data.data;
console.log('Subscription created:', subscription.subscription_code);
return subscription;
}
This is useful when a customer upgrades from a free tier or when you migrate existing users to a subscription model. The authorization must be reusable, and the customer must have been previously charged on your Paystack account.
For trial periods before the first charge, see free trials with Paystack subscriptions.
Recurring charges fail. Cards expire. Accounts run out of funds. Banks decline transactions for risk reasons. In Africa, where card penetration is lower and prepaid/debit cards dominate, failure rates on recurring charges tend to be higher than what you might see in North America or Europe.
How Paystack handles failures depends on which billing path you use.
Subscriptions API failures
When a subscription renewal fails, Paystack:
invoice.payment_failed webhook event.subscription.disable event.Your job is to listen for invoice.payment_failed and take action: notify the customer, show a banner in your app asking them to update their payment method, or restrict access to paid features. Do not wait for the subscription to be disabled. By then the customer has had days of unpaid access.
Authorization code charge failures
When you call charge_authorization and it fails, Paystack does not retry for you. The response comes back with status: "failed" and a gateway_response explaining why. You are responsible for retry logic, timing, and customer communication.
A practical dunning sequence for manual charges:
Do not retry indefinitely. Repeated failed charges look suspicious to banks and can get your Paystack account flagged. Three to five retry attempts over a week is reasonable.
For a full implementation of a dunning email sequence, see building a dunning email sequence. For the technical details of reading and acting on gateway responses, see handling failed recurring charges and dunning.
Paystack sends webhook events at every stage of a subscription's life. Your application logic should be driven by these events, not by polling the API.
The key events:
| Event | When it fires | What you should do |
|---|---|---|
subscription.create |
A new subscription is created | Mark the user as subscribed. Record the subscription_code. |
invoice.create |
Paystack generates an invoice for the next billing cycle | Optional: log the upcoming charge for your records. |
invoice.update |
An invoice is updated, usually after successful payment | Confirm the renewal. Extend the user's access period. |
invoice.payment_failed |
The renewal charge failed | Start your dunning flow. Notify the customer. |
subscription.not_renew |
The subscription will not renew (customer cancelled before the next cycle) | Mark the subscription as ending. Let the user keep access until the current period ends. |
subscription.disable |
The subscription is cancelled or disabled | Revoke access. Mark the subscription as inactive. |
charge.success |
A charge (including subscription renewals) succeeds | Verify the amount. Update your ledger. This fires for all charges, not just subscriptions. |
A common mistake: relying only on charge.success for subscription renewals. That event fires for every successful charge on your account, not just subscription charges. You need to check whether the charge is associated with a subscription by looking at the plan and subscription fields in the event data.
Another common mistake: not handling subscription.not_renew. This event tells you the customer cancelled, but their current billing period has not ended yet. If you revoke access immediately instead of waiting for the period to end, you are taking away time they already paid for.
For a deep dive into each event's payload and processing logic, see invoice events for subscription lifecycles.
Paystack provides two mechanisms for stopping a subscription: disabling it (cancellation) and letting it expire naturally.
Cancelling a subscription via API
You can disable a subscription using the subscription code and the email token:
async function cancelSubscription(subscriptionCode, emailToken) {
const response = await axios.post(
'https://api.paystack.co/subscription/disable',
{
code: subscriptionCode,
token: emailToken,
},
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
console.log('Subscription disabled:', response.data.message);
return response.data;
}
When you disable a subscription, Paystack stops all future charges immediately. You will receive a subscription.disable webhook. The customer is not charged again.
The business question is: do you cut access immediately or at the end of the current billing period? Paystack does not enforce either behavior. That decision is yours. Most SaaS products let the customer keep access until the end of their paid period. You track this in your own database.
Pausing subscriptions
Paystack does not have a native "pause" feature for subscriptions. If you need to pause billing temporarily (customer is on holiday, seasonal business, financial hardship), you have two options:
For a detailed walkthrough of both approaches and the state machine you need, see cancelling and pausing Paystack subscriptions.
Not every billing model fits neatly into "charge X amount every month." Here is how to handle the complex cases.
Usage-based billing
If you charge based on consumption (API calls, messages sent, storage used), the Subscriptions API does not work out of the box because the amount varies each cycle. The pattern:
charge_authorization with the calculated amount.You are building your own billing engine. The subscription is just a concept in your application, not a Paystack subscription object. See usage-based billing with Paystack and building your own billing engine on authorizations.
Metered billing with overages
A hybrid model: charge a base subscription fee using the Subscriptions API, then charge overages separately using charge_authorization when the customer exceeds their included quota. Two charges per period, one managed and one manual. See metered billing and overages.
Proration on plan changes
When a customer upgrades or downgrades mid-cycle, you need to calculate the prorated amount. Paystack does not handle proration automatically. You must:
See proration and plan upgrades for the math and the code.
Coupons and discounts
Paystack does not have a native coupon system for subscriptions. To offer discounts, create a separate plan with the discounted price, or handle the discount logic in your application and charge the reduced amount via charge_authorization. See coupons and discounts on Paystack subscriptions.
Recurring billing in Africa looks different from Silicon Valley SaaS. Here are the patterns that actually work.
SaaS and digital subscriptions
The most straightforward use case. Create plans for each pricing tier, subscribe customers, handle upgrades and downgrades. The Subscriptions API works well here because the amounts and intervals are fixed. The main challenge is churn: card failure rates are higher, so your dunning flow matters more. See building a SaaS billing page on Paystack.
School fee instalment plans
Parents pay school fees in three or four instalments per term. The amounts might differ per instalment, and the schedule follows the school calendar, not a monthly cycle. This is a job for authorization code charges, not the Subscriptions API. Collect the first payment through checkout, store the authorization, then charge the remaining instalments on your schedule. See school fee instalment plans with Paystack.
Loan repayment collection
Fintech lenders collect monthly or weekly repayments from borrowers. The repayment amount may include varying interest, or the borrower may make partial payments. Use authorization code charges with a fixed schedule. Build robust failure handling because missed loan payments have compliance implications. See loan repayment collection with Paystack.
Gym and class memberships
Monthly membership with the option to freeze. Use the Subscriptions API for the recurring charge, and implement pause/resume logic in your application layer. See gym and class membership billing.
Insurance premium collection
Monthly or quarterly premium payments. Fixed amounts make this a good fit for the Subscriptions API, but regulatory requirements around payment receipts and grace periods mean you need careful event handling. See insurance premium collection with Paystack.
Rent collection
Property managers collecting monthly rent from tenants. Fixed amounts, fixed schedule. The Subscriptions API works, but the amounts tend to be large, which increases the risk of insufficient funds failures. Build a solid dunning flow. See rent collection with Paystack.
The common thread across all these use cases: you need to think carefully about what happens when a charge fails. In a SaaS app, you can degrade the service. For school fees or loan repayments, the consequences are more serious. Design your failure handling with the real-world stakes in mind.
After building and debugging recurring billing integrations, these patterns keep coming up.
1. Always verify the first charge before storing the authorization
When a customer completes their first payment, do not store the authorization code from the webhook alone. Call GET /transaction/verify/:reference to confirm the transaction status and amount. Only then save the authorization. This protects against race conditions and spoofed webhooks.
2. Store authorization metadata alongside the code
Do not just save the authorization_code string. Store the last4, card_type, exp_month, exp_year, bank, and reusable fields. You need last4 and card_type to show the customer which card is on file. You need exp_month and exp_year to proactively prompt them to update before the card expires. See handling card expiry.
3. Build a subscription state machine in your database
Do not rely solely on Paystack's subscription status. Maintain your own state in your database: trialing, active, past_due, cancelled, expired. Update this state based on webhook events. This gives you a source of truth you control and lets you implement business logic (grace periods, feature gating) that Paystack does not manage.
4. Use idempotent webhook handlers
Subscription-related webhooks can arrive more than once. Deduplicate by the event's transaction reference or subscription code plus event type. Process each unique event exactly once.
5. Handle the timing gap between subscription creation and the first invoice
When you create a subscription, the first charge happens at the start of the next billing interval, not immediately (unless you are using the checkout flow where the first payment and subscription creation happen together). Do not assume the customer has been charged just because the subscription exists.
6. Monitor subscription churn
Track voluntary cancellations (customer chose to leave) separately from involuntary churn (payment failures). The fix for each is different: better product for voluntary churn, better dunning for involuntary churn. See subscription churn instrumentation.
7. Disable Paystack's default subscription emails if you send your own
Paystack sends email notifications to customers about subscription charges by default. If you send your own branded emails, the customer gets duplicate notifications. You can disable Paystack's emails. See disabling and replacing subscription emails.
This article is the hub for the recurring billing cluster. The spoke articles go deeper on every topic covered above:
Data model and fundamentals
Charging and billing
Failure handling and dunning
Lifecycle management
Real-world implementations
Analytics
McTaba Academy courses at academy.mctaba.com. Pick the skill you need, learn on your schedule.
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