CentraPoint

API

Saved cards API

Let customers save a card on a hosted page (a R1 verification that is credited to their account), list and manage their saved cards, let them pay with a saved card, and charge a saved card from your own billing system.

On this page

Overview#

A customer can save a card for their payments. Your platform sends them to a hosted CentraPoint page where they pay R1.00 by card; the payment gateway stores the card and CentraPoint keeps a reference to it. The R1.00 is credited to the customer and taken off their next invoice. Afterwards the customer can choose the saved card when they pay, and you can have CentraPoint charge the default card when an invoice falls due.

Card numbers never reach CentraPoint or your systems: the API, webhooks and pages only show the brand, the last four digits and the expiry date. Use the account standing endpoint to see whether a customer has a payment method and to show the right banner.

Saved card endpoints
EndpointPurpose
POST /api/v1/customers/{id}/cards/setupStart the hosted add-card page for a customer
GET /api/v1/customers/{id}/cards/setup/{sessionId}Check the outcome of an add-card session
GET /api/v1/customers/{id}/cardsList the customer's saved cards
GET /api/v1/customers/{id}/cards/{cardId}Get one card
POST /api/v1/customers/{id}/cards/{cardId}/defaultMake a card the default
DELETE /api/v1/customers/{id}/cards/{cardId}Remove a card
POST /api/v1/customers/{id}/cards/{cardId}/chargesCharge a saved card from your system (renewals)
GET /api/v1/customers/{id}/standingAccount standing

Requirements#

  • A plan with the REST API.
  • A Netcash Pay Now gateway to save cards. Without one, starting the add-card page returns 409 card_setup_unavailable. Cards are only saved from live card payments.
  • To charge saved cards (paying with a saved card, automatic collection): a Netcash Debit Orders gateway on the same Netcash account with its Card debits option switched on. Visa and Mastercard cards can be charged.

GET /api/v1/me reports what is available: capabilities.savedCards (cards can be saved), capabilities.chargeSavedCards (saved cards can be charged) and capabilities.accountStanding (the standing rules are on).

The card object#

Card
{
  "object": "card",
  "id": "cmgcard00001visa4242abcd",
  "customerId": "cmg2c0s7t0003cust0001abcd",
  "brand": "visa",
  "last4": "4242",
  "expiryMonth": 12,
  "expiryYear": 2028,
  "holderName": "T NKOSI",
  "isDefault": true,
  "status": "active",
  "expiring": false,
  "source": "card_setup",
  "createdAt": "2026-10-05T08:00:00.000Z",
  "lastUsedAt": null,
  "chargeable": true
}
Card fields
FieldTypeDescription
objectrequiredstringcard
idrequiredstringCard ID.
customerIdrequiredstringThe customer the card belongs to.
brandrequiredstringvisa, mastercard, amex, diners or card (unknown).
last4requiredstringLast four digits.
expiryMonthrequiredinteger | null1-12.
expiryYearrequiredinteger | nullFour digits.
holderNamerequiredstring | nullName on the card, as the gateway returned it.
isDefaultrequiredbooleanThe card charged automatically and offered first. A customer has at most one default card.
statusrequiredstringactive; expired (past its expiry month); failed (the bank rejected the card, for example a card token error: add it again); removed.
expiringrequiredbooleanActive and expiring within 30 days.
sourcerequiredstringcard_setup (the add-card page) or checkout (saved while paying).
createdAtrequiredstring (date-time)When the card was first saved.
lastUsedAtrequiredstring (date-time) | nullLast successful charge.
chargeableoptionalbooleanAPI responses only (not webhooks): CentraPoint can charge the card now: active, not expired, Visa or Mastercard, and you have a gateway that charges saved cards. A card that is active but not chargeable still counts as a payment method.

Add a card (hosted page)#

POST/api/v1/customers/{id}/cards/setup

Creates a card setup session and returns its hosted url. Redirect the customer there (for example from your sign-up flow or from the banner your platform shows when a payment method is missing). The page shows your branding, explains the R1.00 verification and sends the customer to the payment gateway. Supports Idempotency-Key.

Request body
FieldTypeDescription
returnUrloptionalstringWhere the customer returns afterwards. Must use https on an origin you allowed under Settings → API keys → Allowed return URLs (like payment links).
makeDefaultoptionalbooleanMake the new card the default. Default true.
expiresInMinutesoptionalinteger5 to 10080 (7 days). Default 1440 (24 hours).
metadataoptionalobjectString map echoed on the session (see metadata).
curl -X POST "https://app.centrapoint.co.za/api/v1/customers/cmg2c0s7t0003cust0001abcd/cards/setup" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "returnUrl": "https://platform.example.co.za/billing",
    "metadata": {
      "platformUserId": "42"
    }
  }'
201 Created
{
  "object": "card_setup_session",
  "id": "cmgsetup0001session0001ab",
  "customerId": "cmg2c0s7t0003cust0001abcd",
  "status": "pending",
  "url": "https://app.centrapoint.co.za/card/Q2x9…",
  "returnUrl": "https://platform.example.co.za/billing",
  "amount": 1,
  "currency": "ZAR",
  "makeDefault": true,
  "cardId": null,
  "failureReason": null,
  "metadata": {
    "platformUserId": "42"
  },
  "expiresAt": "2026-10-06T08:00:00.000Z",
  "completedAt": null,
  "createdAt": "2026-10-05T08:00:00.000Z"
}

The card setup session#

Session status
statusMeaning
pendingOpen: the customer can (try to) save a card. failureReason tells you about the last attempt: declined, cancelled or no_card_saved (paid, but not by card: the R1.00 is still credited).
completedA card was saved: cardId is set.
expiredThe link passed expiresAt without a card being saved.
GET/api/v1/customers/{id}/cards/setup/{sessionId}

Return URL and confirming the result#

After the gateway the customer sees the result on the CentraPoint page and is then sent to your returnUrl with ?cardSetup=<session id>&status=completed|failed|expired. Those query values come from the browser: confirm with GET …/cards/setup/{sessionId}, the card list, or the customer.payment_method.added webhook before you rely on them.

The R1 verification credit#

The R1.00 card payment is a normal payment in every way: a transaction with type: "card_setup", a payment.complete webhook, a receipt email to the customer (when you send receipts) and, with an accounting integration, a payment in your books. CentraPoint then issues a credit note for R1.00 that is applied automatically to the customer's oldest open invoice, or to the next invoice you issue (credit_note.created, credit_note.issued and credit_note.applied webhooks, synced to your books as well). Each R1.00 payment is credited once.

List a customer's cards#

GET/api/v1/customers/{id}/cards

Returns { "object": "list", "data": [card, …] }, the default card first. Removed cards are left out unless you pass includeRemoved=true.

curl -X GET "https://app.centrapoint.co.za/api/v1/customers/cmg2c0s7t0003cust0001abcd/cards" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Get a card#

GET/api/v1/customers/{id}/cards/{cardId}

Set the default card#

POST/api/v1/customers/{id}/cards/{cardId}/default

Makes the card the default and returns it. An expired, failed or removed card is refused with 400. Not idempotent; repeating it is harmless.

Remove a card#

DELETE/api/v1/customers/{id}/cards/{cardId}

Stops using the card and returns it with status: "removed". A charge of the card that was not sent to the bank yet is cancelled. When it was the default, the newest other usable card becomes the default. Removing it again returns 404.

Paying with a saved card#

On the hosted pay page of a payment link or invoice issued to a customer, that customer sees their saved cards (brand, last four digits, expiry) next to "a new card or another method", and confirms the amount before the card is charged. Links anyone can open (no customer) and recurring links never show saved cards. You can switch the option off under Settings → Cards & standing.

A saved card is charged by the bank as a card debit: the payment stays pending until the bank confirms it, usually within 1-3 business days, and then completes (payment.complete, invoice.paid) or fails (payment.failed). While it is pending the pay page shows that a card payment is in progress instead of taking a second payment. Paying with a new card works as before and completes at once; the customer can tick Save this card for future payments to save it as well.

A customer pays at most R1,000,000.00 with a saved card at a time, and at most 10 saved card payments a day: payments on pay pages and checkouts count together with your API charges of that customer.

Anyone who has a link can open it, including the system that created it. So a payment link made with POST /api/v1/payment-links, an invoice's pay link created or returned through the API (creating, issuing or sending an invoice, converting a quote), and a payment retry link made with the API only offer saved cards while the API key that made them is allowed to Charge saved cards (Settings → API keys, see key permissions). The same applies to a customer signed in to the portal with a sign-in link from POST /api/v1/customers/{id}/portal-link with email: false: they pay with saved cards (an invoice in the portal, a hosted checkout) only while that key may charge saved cards. The key's current permission counts: withdrawing it, or switching the key off, stops saved cards on its links at once. Links sent from the dashboard and by CentraPoint itself are not affected.

Hosted checkout: signed-in customers only#

Anyone can open a hosted checkout page and type an email address, so saved cards are offered there only to a customer who is signed in to your customer portal (the portal session is valid on your checkout pages too). They see their saved cards and pay with the email they are signed in with: for a product, or for the first payment of a subscription plan (the subscription starts when the bank confirms the payment; later renewals are invoiced, and charged to the default card when automatic collection is on). Recurring products, which need a card subscription at the gateway, always use a new card. Everyone who is not signed in pays with a new card as before.

Automatic collection#

With Charge the default card when an invoice falls due switched on (Settings → Cards & standing), CentraPoint charges the customer's default card once for each invoice on or after its due date. Invoices of subscriptions collected by debit order, manually or by your own platform are left alone, as are invoices with a payment already in progress. If the charge fails, the customer can still pay on the pay page.

Charge a saved card from your system#

POST/api/v1/customers/{id}/cards/{cardId}/charges

Charges a customer's saved card without the customer present: for example your platform's own renewal invoice. Use default as cardId to charge the customer's default card. Supports Idempotency-Key (for example your invoice number plus the attempt), so a retried request never charges twice.

Request body
FieldTypeDescription
amountrequirednumberAmount in ZAR, above zero, at most two decimals (maximum 1 000 000).
currencyoptionalstringOnly ZAR (the default).
descriptionrequiredstringWhat the charge is for (1-255 characters): shown in the dashboard, on the receipt and in your books.
externalReferencerequiredstringYour reference, for example your invoice number (1-100 characters). At most one pending or successful charge per externalReference in your organisation; after a failed charge the same value may be charged again.
metadataoptionalobjectString map returned with the charge and in its payment webhooks (see metadata).
curl -X POST "https://app.centrapoint.co.za/api/v1/customers/cmg2c0s7t0003cust0001abcd/cards/default/charges" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 199,
    "description": "Pro plan - October 2026",
    "externalReference": "INV-1001",
    "metadata": {
      "platformInvoiceId": "1001"
    }
  }'
201 Created
{
  "object": "card_charge",
  "id": "cmgchg0001charge0001abcd",
  "reference": "CPA-3F9A1C7B2E4D6F80-1",
  "status": "pending",
  "amount": 199,
  "currency": "ZAR",
  "description": "Pro plan - October 2026",
  "externalReference": "INV-1001",
  "metadata": {
    "platformInvoiceId": "1001"
  },
  "customerId": "cmg2c0s7t0003cust0001abcd",
  "card": {
    "id": "cmgcard00001visa4242abcd",
    "brand": "visa",
    "last4": "4242"
  },
  "failureReason": null,
  "paidAt": null,
  "createdAt": "2026-10-08T06:00:00.000Z"
}

The card charge#

A charge is a payment (a transaction of type card_debit): its reference works with GET /api/v1/transactions/{reference}, which also shows your externalReference and metadata. status is pending until the bank confirms the charge, then complete or failed (with failureReason). Refunds work as for any payment.

Results, errors and webhooks#

The bank charges the card as a card debit, usually confirmed within 1-3 business days. You receive payment.complete or payment.failed with type: "card_debit", your externalReference, metadata and customer: match them to your invoice. While the charge is pending, treat your invoice as being paid (do not lock the customer and do not charge again). A card the bank rejects with a card token error becomes failed; an expired card becomes expired (the card list and customer.payment_method.* webhooks show it). After a failed charge, send the customer a payment link issued to them (they can choose another saved card or pay with a new card) or ask them to add a card.

Charge errors
StatuserrorWhen
400invalid_requestAmount not above zero or with more than two decimals, a currency other than ZAR, unknown fields, or an inactive customer.
403permission_requiredThe API key may not charge saved cards.
404not_foundNo such customer or card (a removed card included).
409charge_existsA pending or successful charge with this externalReference exists; it is returned in charge (not when it is another customer's).
409no_default_carddefault was used and the customer has no default card.
409card_not_chargeableThe card is expired or failed, or not a Visa / Mastercard your gateway can charge.
409card_charges_unavailableYou have no gateway that charges saved cards (Netcash Debit Orders with Card debits).
429rate_limitedMore than 60 charges a minute in your organisation or 10 a day for one customer (Retry-After says when to try again), or the key's request limit.

Responses are stored under an Idempotency-Key for 24 hours, refusals included: after fixing the cause (for example allowing the key, or adding the gateway), retry with a new key.

Platforms with their own plans and invoices#

If your platform keeps its own plans and invoices and uses CentraPoint to take payments:

  • Saving a card: send the customer to the url of POST …/cards/setup with your returnUrl (right after sign-up on a paid plan, and from your billing page). The R1.00 is a payment you received: take R1.00 off your own next invoice for that customer when you receive customer.payment_method.added.
  • The customer pays an invoice: create a payment link with customerId, the invoice amount and your invoice number as externalReference (not recurring). The customer sees their chargeable saved cards and can choose one, or pay with a new card. Saved cards are offered only when your key may charge saved cards (links made with the API).
  • Renewals: when your invoice falls due, charge the default card with this endpoint (one charge per invoice, your invoice number as externalReference) and mark your invoice paid on payment.complete.
  • Payment method and standing: a customer has a payment method when one of their cards has status: "active". The account standing endpoint only knows invoices in CentraPoint, so apply the grace and lock rules to your own invoices (see Platforms with their own invoices).

Webhooks#

Saved card events
EventWhendata
customer.payment_method.addedA card was saved (or a removed, expired or failed card was saved again).The card object plus customer: { id, externalReference }.
customer.payment_method.removedA card was removed (API, dashboard or portal).The card (status removed).
customer.payment_method.expiringA card expires within 30 days. Sent once per card.The card.

Account standing changes are sent as customer.standing.changed; see Account standing and Webhooks for signatures and retries.

Errors#

Saved card errors
StatuserrorWhen
400invalid_requestUnknown body fields, a return URL that is not https or not on an allowed origin, an inactive customer, or a card that cannot be the default.
404not_foundNo such customer, card or session in your organisation.
409card_setup_unavailableNo payment gateway that can save cards.

Errors of the charge endpoint are listed under Results, errors and webhooks. Other errors follow the usual error format.