Guides
Recurring billing
Two ways to bill customers repeatedly: gateway-managed card subscriptions, or an invoice per billing cycle.
On this page
Choosing an approach#
| Card subscription | Invoice per cycle | |
|---|---|---|
| How the customer pays | Card charged automatically each cycle by the gateway | Customer pays each invoice via its pay link (or EFT) |
| Who schedules | The gateway | Your system (or a scheduled job) |
| Amount per cycle | Fixed | Can change every cycle (usage, pro-rata, add-ons) |
| Gateways | PayFast, Netcash Pay Now (ZAR) | Any gateway that accepts the currency, plus offline payments |
| Plan | Recurring card billing feature (Growth and above) | Invoicing feature |
| API | Payment links + Subscriptions | Customers + Invoices |
Option 1: card subscriptions#
The customer pays once through a recurring payment link; the gateway stores the card and charges it on each cycle. Gateways with recurring support are PayFast and Netcash Pay Now. Only gateways that support recurring payments are offered to the payer on a recurring link.
Start a subscription#
curl -X POST "https://app.centrapoint.co.za/api/v1/payment-links" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
-H "Idempotency-Key: 0f2e4d6c-8b1a-4c3e-9f5d-7a2b4c6e8d10" \
-H "Content-Type: application/json" \
-d '{
"title": "Pro plan",
"amount": 499,
"currency": "ZAR",
"externalReference": "SUB-ACME-PRO",
"customerId": "cmg2c0s7t0003cust0001abcd",
"recurring": {
"frequency": "monthly"
},
"metadata": {
"plan": "pro"
},
"returnUrl": "https://app.example.co.za/billing/done"
}'recurring.frequency:monthly,quarterly,biannuallyorannually.recurring.amount(optional) is charged on each later cycle; it defaults to the link amount. Use it for a different first payment, e.g. a setup fee.- The request fails with
400if your plan lacks recurring billing or none of your enabled gateways can bill the currency automatically.
When the first payment completes you receive subscription.activated (and payment.complete); the subscription then appears in GET /api/v1/subscriptions?externalReference=SUB-ACME-PRO, and the payment carries its subscriptionId.
Renewals#
Each later collection is a new transaction with its own reference, renewal: true, parentReference (the first payment) and subscriptionId. You get payment.complete or payment.failed for each one, plus a subscription event.
Subscription webhooks#
| Event | When | Typical action |
|---|---|---|
subscription.activated | First payment completed | Grant access until periodEnd |
subscription.charged | Renewal collected | Extend access to the new periodEnd |
subscription.payment_failed | Renewal failed | Warn the customer; suspend if it stays unpaid |
subscription.cancelled | Cancelled via API, dashboard or gateway | Stop access at the end of the paid period |
subscription.paused | Paused via API or dashboard | Keep access until the paid period ends; stop renewing it |
subscription.resumed | Resumed via API or dashboard | Renew access again from the next collection |
data is the subscription object plus subscriptionId; the first three events add payment (reference, amount, currency, status, paidAt) and periodStart/periodEnd for the period paid. See Subscription events.
Pausing, resuming and cancelling#
Pause a PayFast subscription with POST /api/v1/subscriptions/{id}/pause and resume it with /resume; Netcash Pay Now subscriptions can only be cancelled. See the rules. To cancel:
curl -X POST "https://app.centrapoint.co.za/api/v1/subscriptions/cmg2s5b8k0007sub0001abcd/cancel" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY"Returns the subscription with status: "cancelled" and sends subscription.cancelled. See Subscriptions errors for gateway failures.
Option 2: an invoice per cycle#
Your system decides what to bill each cycle and creates an invoice; CentraPoint emails it with a pay link and tells you when it is paid. CentraPoint does not schedule these invoices for you: run your own job (for example on the 1st of each month).
Each cycle#
- Make sure the customer exists (upsert once with POST /api/v1/customers and your customer ID as
externalReference). - Create the invoice with an
externalReferenceunique to the customer and cycle, andsend: true. Because creation is idempotent onexternalReference, re-running the job for the same cycle never bills twice.
curl -X POST "https://app.centrapoint.co.za/api/v1/invoices" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"externalReference": "CRM-1001"
},
"externalReference": "CRM-1001-2026-10",
"lines": [
{
"description": "Pro plan - October 2026",
"quantity": 1,
"unitPrice": 434.78
},
{
"description": "Extra users (3)",
"quantity": 3,
"unitPrice": 43.48
}
],
"metadata": {
"period": "2026-10"
},
"send": true
}'Tracking payment#
- Listen for
invoice.paid,invoice.partially_paidandinvoice.cancelledwebhooks;datais the invoice. - If the customer pays by EFT, record it with POST /api/v1/invoices/{id}/payments.
- Chase unpaid invoices with
GET /api/v1/invoices?status=overdueand send them again.
Bank debit orders#
For collecting directly from South African bank accounts, CentraPoint also supports Netcash debit orders from the dashboard. See Debit orders & reconciliation.