Building Subscription Billing with DGateway's API
DGateway is a unified payment and commerce platform for Africa, offering a single API to accept mobile money (MTN, Airtel) and card payments. Its subscription API makes it straightforward to implement recurring billing for SaaS products, memberships, and content subscriptions.
Recurring payments are the engine behind SaaS products, membership sites, content subscriptions, and many other modern business models. But implementing subscription billing — especially with mobile money — is notoriously complex.
DGateway's subscription API handles the hard parts for you: plan management, customer enrollment, automatic renewals, failed payment retries, and lifecycle notifications. This tutorial shows you how to build a complete subscription billing system.
How Subscription Billing Works on DGateway
The subscription lifecycle on DGateway follows four key concepts:
- Plans — Define what you are selling (name, price, billing interval).
- Subscriptions — Link a customer to a plan.
- Invoices — Generated automatically for each billing cycle.
- Webhooks — Notify your application about subscription events.
You create plans once. Then you subscribe customers to those plans. DGateway handles the rest — billing them on schedule, retrying failed payments, and notifying you about every state change.
Step 1: Create a Plan
A plan defines the terms of the subscription. Create one via the API:
curl -X POST https://api.dgateway.io/v1/plans \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro Plan",
"amount": 50000,
"currency": "UGX",
"interval": "monthly",
"description": "Full access to all Pro features",
"trial_days": 7
}'The response includes a plan_id that you will use when subscribing customers:
{
"id": "plan_pro_monthly",
"name": "Pro Plan",
"amount": 50000,
"currency": "UGX",
"interval": "monthly",
"trial_days": 7,
"created_at": "2026-03-20T10:00:00Z"
}Supported intervals include daily, weekly, monthly, and yearly. The trial_days field is optional — if set, the customer will not be charged until the trial period ends.
Step 2: Subscribe a Customer
When a customer chooses to subscribe, create a subscription by linking them to a plan:
curl -X POST https://api.dgateway.io/v1/subscriptions \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_id": "plan_pro_monthly",
"customer_phone": "256771234567",
"customer_email": "user@example.com",
"provider": "mtn",
"metadata": {
"user_id": "usr_456",
"account_type": "pro"
}
}'DGateway will immediately initiate the first payment (or start the trial period if configured). The response tells you the subscription status:
{
"id": "sub_789",
"plan_id": "plan_pro_monthly",
"status": "active",
"current_period_start": "2026-03-20T10:00:00Z",
"current_period_end": "2026-04-20T10:00:00Z",
"trial_end": "2026-03-27T10:00:00Z",
"next_billing_date": "2026-03-27T10:00:00Z",
"metadata": {
"user_id": "usr_456",
"account_type": "pro"
}
}Step 3: Handle Renewal Webhooks
When a billing cycle arrives, DGateway automatically charges the customer and sends webhook notifications. Your application needs to handle these events to manage access:
app.post("/webhooks/subscriptions", async (req, res) => {
const { event, data } = req.body;
switch (event) {
case "subscription.renewed":
// Payment succeeded — extend access
await extendAccess(data.metadata.user_id, data.current_period_end);
break;
case "subscription.payment_failed":
// Payment failed — DGateway will retry
await notifyUser(data.metadata.user_id, "payment_failed");
break;
case "subscription.cancelled":
// Subscription ended — revoke access at period end
await scheduleAccessRevocation(
data.metadata.user_id,
data.current_period_end
);
break;
case "subscription.expired":
// All retries exhausted — subscription is over
await revokeAccess(data.metadata.user_id);
break;
}
res.status(200).send("OK");
});Key Subscription Events
| Event | When It Fires |
|---|---|
subscription.created | A new subscription is created |
subscription.renewed | A renewal payment succeeded |
subscription.payment_failed | A renewal payment attempt failed |
subscription.cancelled | The subscription was cancelled (by you or the customer) |
subscription.expired | The subscription expired after all retries failed |
subscription.trial_ending | The trial period ends in 3 days |
Step 4: Handle Failed Payments
Payment failures are inevitable, especially with mobile money where customers may have insufficient balance or their phone may be off. DGateway's smart retry system handles this automatically.
When a renewal payment fails, DGateway retries using the following schedule:
- Retry 1 — 24 hours after the initial failure
- Retry 2 — 48 hours after retry 1
- Retry 3 — 72 hours after retry 2
Before each retry, DGateway sends the customer an SMS reminder to ensure they have sufficient balance.
During the retry period, the subscription status changes to past_due. You decide whether to maintain access during this grace period or restrict it. Most businesses maintain access to give the customer time to resolve the payment issue.
If all retries fail, the subscription moves to expired and DGateway sends a final notification.
Step 5: Manage Cancellations
Customers should be able to cancel their subscriptions. You can cancel a subscription via the API:
curl -X POST https://api.dgateway.io/v1/subscriptions/sub_789/cancel \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"cancel_at_period_end": true
}'Setting cancel_at_period_end to true means the customer retains access until the end of their current billing period. Setting it to false cancels immediately.
After cancellation, the subscription status changes to cancelled and no further billing attempts are made.
Step 6: Upgrade and Downgrade Plans
To change a customer's plan, update the subscription:
curl -X PATCH https://api.dgateway.io/v1/subscriptions/sub_789 \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_id": "plan_enterprise_monthly",
"proration": true
}'When proration is true, DGateway calculates the difference between the old and new plan for the remaining days in the current billing period and adjusts the next charge accordingly.
Putting It All Together
Here is a practical architecture for a SaaS application using DGateway subscriptions:
- Signup flow — User picks a plan on your pricing page. Your backend creates a DGateway subscription. The first payment (or trial) begins.
- Access control — Your middleware checks the user's subscription status before granting access to premium features. Store the
current_period_endtimestamp and validate against it. - Webhook handler — Listens for renewal, failure, and cancellation events. Updates the user's access accordingly.
- Settings page — Lets users view their current plan, switch plans, update payment details, and cancel.
- Admin dashboard — Shows your subscription metrics: active subscribers, churn rate, MRR, and failed payments.
Best Practices
- Always use webhooks for subscription status changes. Do not rely on polling.
- Be generous with grace periods. Mobile money customers may need a day or two to top up their balance.
- Send reminders before billing. A simple SMS 24 hours before renewal reduces failed payments significantly.
- Store subscription metadata in your database. Do not rely solely on DGateway for your subscription state — keep a local copy that syncs via webhooks.
- Test the full lifecycle in sandbox mode before going live. Create a subscription, trigger a renewal, simulate a failure, and test cancellation.
Subscription billing does not have to be painful. With DGateway's API handling the billing engine, you can focus on building the product your customers are paying for.