Getting started
Idempotency, metadata & references
Idempotency-Key retries, metadata rules, payment reference formats and how references relate to payment links.
On this page
Payment references#
Every payment attempt is a transaction with a unique payment reference. CentraPoint generates the reference when the payer starts checkout, and sends it to the gateway as the merchant reference (for example PayFast's m_payment_id, Paystack's reference, DPO's CompanyRef). The reference is how you look up a payment with GET /api/v1/transactions/{reference}, and how gateway notifications are matched to transactions.
Reference format#
CP-<YYYYMMDD>-<8 upper-case hex characters>
CP-20260925-9F3A1C7BYYYYMMDDis the UTC date the checkout started.- The last part is random (4 bytes), so references are unguessable in practice and unique across the platform.
- Payments created before 25 September 2026 have references in the older
GSS-YYYYMMDD-XXXXXXXXformat. They keep those references, remain valid for every API call, and are recognised by reconciliation alongsideCP-references. - Treat references as opaque strings up to 190 characters. Do not parse the date out of them; use
createdAtinstead.
Recurring renewals#
With gateway-managed subscriptions (for example PayFast), each later charge re-uses the original merchant reference. CentraPoint records each renewal as its own transaction with the reference <original reference>-<gateway payment id> and type: "recurring", for example CP-20260925-9F3A1C7B-2458799.
Debit order references#
Debit order collections use the mandate's account reference plus the action date, for example DO7F3A1C9B2E-20261001. See Debit orders & reconciliation.
EFT order references#
EFT orders use a deposit reference such as EFT-7K3Q9P2M (your prefix plus 8 characters without 0, O, 1, I or L) as their payment reference.
Payment links vs. references#
A payment link and a transaction are different things:
| Payment link | Transaction | |
|---|---|---|
| Created by | POST /api/v1/payment-links (or the dashboard) | The payer starting checkout on the link |
| Identifier | id (and your optional externalReference) | reference |
| How many | One | Zero or more per link: every checkout attempt creates one (a retry after a failed or cancelled attempt creates another) |
Finding the references for your order#
Recommended pattern:
- Create the link with
externalReferenceset to your order ID ("ORDER-1001"). - When you need the outcome, call
GET /api/v1/payment-links?externalReference=ORDER-1001(orGET /api/v1/payment-links/{id}if you stored the link ID). Each link includes apaymentsarray with thereferenceandstatusof every checkout attempt, newest first. - Confirm the attempt that matters with
GET /api/v1/transactions/{reference}. The transaction echoespaymentLinkIdandexternalReference, so you can check it belongs to your order.
Idempotency#
Networks fail: a request can time out after CentraPoint has already acted on it. GET and PATCH requests are safe to repeat. For POST requests, use an Idempotency-Key and/or an externalReference so a retry never creates a second link, invoice or payment.
The Idempotency-Key header#
Idempotency-Key: 3f6c2a4e-8d1b-4f7a-9c2e-5b1d0a7e6f31- Supported on the v1
POSTendpoints: payment links, customers, invoices (create, send, cancel, payments) and EFT orders (create, cancel).POST /api/v1/subscriptions/{id}/canceland the EFT proof upload ignore it; they are naturally safe to repeat. - Up to 200 characters. Generate a new unique value (such as a UUID) for each distinct operation, and reuse it only when retrying that operation.
- Keys are remembered for 24 hours, scoped to your organisation, the HTTP method and the exact request path (for example
POST /api/v1/invoices/inv_A/send). The same key on a different path, such as another invoice, is a separate key. A retry with the same key and the same body returns the original response (status and body) with the headerIdempotent-Replayed: true, without doing the work again. - The same key with a different body returns
409 idempotency_conflict. - Successful and
4xxresponses are stored and replayed.5xxresponses are not stored, so retrying after a server error runs the request again. - The body is compared byte for byte: serialise it the same way when retrying.
Idempotent by externalReference#
- Invoices:
POST /api/v1/invoiceswith anexternalReferencethat already exists returns the existing invoice (200) and changes nothing. - Customers:
POST /api/v1/customerswith anexternalReferenceis an upsert: repeated calls update the same customer. - EFT orders:
POST /api/v1/eft-orderswith theexternalReferenceof an open order returns that order (200); a different amount or currency is a400. - Payment links:
externalReferenceis not unique, so repeating a create without an Idempotency-Key makes another link. After a timeout without a key, checkGET /api/v1/payment-links?externalReference=<order id>before creating again. WithsingleUse: true(the default) and anexpiresAt, a stray duplicate cannot be paid twice or indefinitely.
Metadata#
Payment links, customers, invoices and EFT orders accept metadata: your own key/value data, such as IDs from your CRM or cart. CentraPoint stores it and returns it; it never affects processing.
{
"metadata": {
"crmId": "0061x00000AbCdE",
"cartId": "c_8841",
"plan": "pro"
}
}| Rule | Limit |
|---|---|
| Shape | A flat JSON object (no arrays or nested objects) |
| Keys | 1–40 characters: letters, digits and _ . : -; at most 50 keys |
| Values | Strings up to 500 characters. Numbers and booleans are stored as strings; null values are dropped. |
| Total size | At most 4000 characters as JSON |
- Always returned as an object:
{}when empty. - Payment link and EFT order metadata is echoed on their transactions and
payment.*webhooks; payment link metadata also appears on subscriptions it starts. - On
PATCH /api/v1/customers/{id},metadatareplaces the whole map; sendnullto clear it. - Don't store secrets or card data in metadata.
How CentraPoint de-duplicates gateway notifications#
Gateways often send the same notification more than once. CentraPoint applies each status change exactly once:
- A notification that repeats the current status is recorded as a duplicate and ignored.
- Status only moves forward:
pending→cancelled→failed→complete. A latefailednever overwritescomplete, but a latecompletedoes replacecancelledorfailed. - Refunded transactions (
refunded,partially_refunded) are not changed by gateway notifications. - Fulfilment (marking an invoice paid, deactivating a single-use link, storing a subscription token) runs only in the update that actually moves the status, even under concurrent notifications.