Paystack Subscriptions and Recurring Billing: Complete Guide
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.
How Paystack Subscriptions Work
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:
- You create a Plan on Paystack with a name, amount, interval (daily, weekly, monthly, quarterly, annually), and currency.
- A customer makes their first payment through Paystack Popup, Redirect, or the Charge API. Paystack saves their card and returns an authorization code.
- You create a Subscription linking the customer to the plan. Paystack now owns the billing schedule.
- On each billing date, Paystack creates an Invoice, charges the customer's saved card using the authorization, and sends you webhook events for every state change.
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.
The Data Model: Plans, Subscriptions, and Authorizations
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.
Creating Plans
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:
- You cannot change a plan's amount or interval after creation. If you need a price change, create a new plan and migrate subscribers. See grandfathering old prices for the migration pattern.
- Plan codes are stable. Once created, a plan code does not change. Hard-code them in your config or store them in a settings table.
- One plan per price point per interval. If you offer monthly and annual billing for the same tier, that is two plans. See annual vs monthly billing logic.
- Invoice limit. You can set
invoice_limitto 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.
Subscribing Customers
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.
Handling Failed Charges and Dunning
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:
- Sends an
invoice.payment_failedwebhook event. - Retries the charge automatically over the following days. The exact retry schedule is managed by Paystack.
- If all retries fail, Paystack may disable the subscription and send a
subscription.disableevent.
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:
- Immediate: Log the failure. Mark the invoice as unpaid in your database.
- After 1 hour: Retry the charge once. Some failures (insufficient funds) are temporary.
- After 24 hours: Send the customer an email or SMS asking them to add funds or update their card.
- After 3 days: Retry again. Send a second notification.
- After 7 days: Final retry. Warn the customer their access will be restricted.
- After 10 days: Downgrade or suspend the account. Stop retrying.
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.
Subscription Lifecycle Events
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.
Cancellation and Pausing
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:
- Disable and re-enable. Cancel the subscription, then create a new one when the customer wants to resume. The downside: you lose the subscription history, and the billing date resets.
- Switch to manual charges. Cancel the managed subscription and use authorization code charges on your own schedule. When the pause ends, resume charging or create a new subscription.
For a detailed walkthrough of both approaches and the state machine you need, see cancelling and pausing Paystack subscriptions.
Advanced Patterns: Usage-Based, Metered, and Proration
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:
- Track usage in your own database throughout the billing period.
- At the end of the period, calculate the total amount owed.
- Charge the customer using
charge_authorizationwith 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:
- Calculate days remaining on the old plan and the cost of those days.
- Calculate the cost of the new plan for the remaining days.
- Charge or credit the difference.
- Cancel the old subscription and create a new one on the new plan.
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.
Real-World Use Cases Across Africa
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.
Integration Best Practices
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.
What This Cluster Covers
This article is the hub for the recurring billing cluster. The spoke articles go deeper on every topic covered above:
Data model and fundamentals
- Plans and subscriptions: the full data model
- Authorization codes: how Paystack recurring billing really works
- Reusable vs non-reusable authorizations
- Storing authorization codes securely
- Subscriptions API vs manual recurring charges: choosing correctly
Charging and billing
- Charging a saved card with authorization codes
- Building your own billing engine on authorizations
- Usage-based billing with Paystack
- Metered billing and overages
- Proration and plan upgrades
- Annual vs monthly billing logic
- Coupons and discounts on subscriptions
- Grandfathering old prices on plans
Failure handling and dunning
- Handling failed recurring charges and dunning
- Building a dunning email sequence
- Handling card expiry in recurring billing
Lifecycle management
- Free trials with Paystack subscriptions
- Cancelling and pausing subscriptions
- Invoice events for subscription lifecycles
- Disabling and replacing subscription emails
Real-world implementations
- Building a SaaS billing page on Paystack
- Building a membership site on Paystack
- School fee instalment plans with Paystack
- Loan repayment collection with Paystack
- Gym and class membership billing
- Insurance premium collection with Paystack
- Rent collection with Paystack
Analytics
McTaba Academy courses at academy.mctaba.com. Pick the skill you need, learn on your schedule.
Key Takeaways
- ✓Paystack offers two recurring billing paths: the managed Subscriptions API (plans, automatic renewals) and manual recurring charges via authorization codes.
- ✓A Plan defines the price and interval. A Subscription links a customer to a plan. An Authorization stores the saved payment method.
- ✓Authorization codes are reusable tokens Paystack returns after a first successful charge. You store the code and use it to charge the customer later without them re-entering card details.
- ✓Failed recurring charges need a dunning strategy. Paystack retries subscription renewals automatically, but you must handle failures for manual authorization code charges yourself.
- ✓Subscription lifecycle events (subscription.create, invoice.create, invoice.payment_failed, subscription.disable) drive your application logic. Listen for them via webhooks.
- ✓You can pause, cancel, or let subscriptions expire. Each has different implications for your billing state machine.
- ✓Real-world African use cases like school fees, loan repayment, and insurance premiums often need authorization code charges rather than the subscriptions API because the amounts or schedules vary.
Frequently Asked Questions
- Can I charge different amounts each billing cycle with Paystack subscriptions?
- No. The Subscriptions API charges the fixed amount defined in the plan every cycle. If you need variable amounts, use authorization code charges via the charge_authorization endpoint instead. You store the authorization from the first payment and charge whatever amount you need on your own schedule.
- What happens when a customer's card expires on a Paystack subscription?
- The next renewal charge will fail. Paystack sends an invoice.payment_failed webhook. It retries the charge over the following days. If all retries fail, the subscription may be disabled. You should proactively track card expiry dates from the authorization object and prompt customers to update their payment method before expiry.
- How do I let a customer update their card on a Paystack subscription?
- Have the customer make a new payment through Paystack checkout. This generates a new authorization code. Then update the subscription to use the new authorization, or cancel the old subscription and create a new one with the updated authorization. There is no API to directly swap the card on an existing subscription without a new payment.
- Does Paystack support free trials on subscriptions?
- Not natively with a built-in trial field. You can implement trials by creating the subscription to start at a future date, or by managing the trial period in your application and only creating the Paystack subscription when the trial ends. Some developers collect card details with a zero-amount charge during signup and subscribe the customer when the trial expires.
- Can I use mobile money for recurring payments on Paystack?
- Mobile money authorizations on Paystack are typically not reusable, which means you cannot charge them again automatically. Recurring billing on Paystack requires a reusable authorization, which currently means card payments in most supported countries. Check the Paystack documentation for the latest on mobile money recurring support in your specific country.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon