API
EFT orders API
Issue unique deposit references for bank transfers, upload proof of payment and track each order until your team approves it.
On this page
Overview#
An EFT order asks a payer to transfer a fixed amount into your own bank account using a unique deposit reference. The payer (or your system) uploads proof of payment, and your team approves the deposit in the dashboard once it reflects. Approval completes the payment like any other, so you receive payment.complete and receipts and accounting sync happen as usual. See the Accepting EFT with proof of payment guide for the full flow.
Requires API access, the Payment links plan feature, and an enabled Manual EFT (bank transfer) gateway with your bank details.
| Endpoint | Purpose |
|---|---|
POST /api/v1/eft-orders | Create (idempotent on externalReference for open orders) |
GET /api/v1/eft-orders | List, filter by reference, customer or status |
GET /api/v1/eft-orders/{id} | Get one order |
POST /api/v1/eft-orders/{id}/proof | Upload proof of payment (multipart) |
POST /api/v1/eft-orders/{id}/cancel | Cancel an unpaid order |
The EFT order object#
{
"id": "cmg3e1f2t0009eft0001abcd",
"reference": "EFT-7K3Q9P2M",
"status": "awaiting_payment",
"amount": 1250,
"currency": "ZAR",
"description": "Order #1001",
"externalReference": "ORDER-1001",
"metadata": {
"cartId": "c_8841"
},
"customer": {
"id": "cmg2c0s7t0003cust0001abcd",
"externalReference": "CRM-1001"
},
"bankDetails": {
"bankName": "FNB",
"accountName": "Example Traders (Pty) Ltd",
"accountNumber": "62000000000",
"branchCode": "250655",
"accountType": "current"
},
"paymentReference": "EFT-7K3Q9P2M",
"payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
"proofs": [],
"rejectionReason": null,
"expiresAt": "2026-10-02T08:00:00.000Z",
"reviewedAt": null,
"paidAt": null,
"createdAt": "2026-09-25T08:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
idrequired | string | EFT order ID. |
referencerequired | string | Unique deposit reference, e.g. EFT-7K3Q9P2M: your prefix (default EFT) and 8 characters without the easily confused 0, O, 1, I and L. It is also the transaction reference. |
paymentReferencerequired | string | What the payer must type as the payment reference (same as reference). |
payUrlrequired | string | Signed public payer page with the bank details, reference and a proof upload. No login needed; a tampered link returns 404. Share it instead of building your own upload. |
statusrequired | string | See Statuses. |
amount, currencyrequired | number, string | Amount to transfer. Currencies: ZAR, NAD, BWP, LSL, SZL. |
bankDetailsrequired | object | bankName, accountName, accountNumber, branchCode, accountType from your Manual EFT gateway. Show them to the payer. |
descriptionrequired | string | null | |
externalReferencerequired | string | null | Your ID. |
metadatarequired | object | Your key/value data (rules). |
customerrequired | object | null | { id, externalReference } |
proofsrequired | object[] | id, fileName, contentType, size (bytes), source and createdAt of each uploaded file, oldest first. source is api (your system), dashboard (your staff) or payer (the payer on the payer page). |
rejectionReasonrequired | string | null | Why staff rejected the latest proof. |
expiresAtrequired | string | null | After this, an order still awaiting payment (or rejected) expires. The payer gets one reminder about 48 hours before. |
reviewedAtrequired | string | null | When staff approved or rejected it. |
paidAtrequired | string | null | When the payment completed (status paid). |
createdAtrequired | string |
Statuses#
| status | Meaning | Next |
|---|---|---|
awaiting_payment | Created; waiting for the payer to transfer and send proof. Staff may approve it without proof. | awaiting_review, expired, cancelled, paid |
awaiting_review | Proof uploaded; waiting for your staff. Does not expire. | paid, rejected, cancelled |
rejected | Staff rejected the proof (rejectionReason). The payer can upload new proof until expiry. | awaiting_review, expired, cancelled, paid |
paid | Approved; the payment is complete. | – |
expired | Passed expiresAt without proof (or after a rejection). Staff can still approve it if the money arrives. | paid |
cancelled | Cancelled through the API or dashboard. | – |
Create an EFT order#
/api/v1/eft-orders| Field | Type | Description |
|---|---|---|
amountrequired | number | Greater than 0, at most 100 000 000. Rounded to cents. |
currencyoptional | string | Default ZAR. Must be one the Manual EFT gateway supports (ZAR, NAD, BWP, LSL, SZL). |
customeroptional | object | { "id": "…" } or { "externalReference": "…" }. |
customerIdoptional | string | Alternative to customer; send one, not both. |
descriptionoptional | string | Up to 1000 characters. |
externalReferenceoptional | string | 1–190 characters. See idempotency below. |
metadataoptional | object | Key/value data. |
expiresAtoptional | string (date-time) | ISO 8601 with Z or an offset; 5 minutes to 90 days ahead. Default: the gateway's expiry days (7 unless configured). |
notifyPayeroptional | boolean | Default false. Email the customer the bank details and payUrl when the order is created. Needs a customer with an email address, otherwise 400. Not re-sent when an existing open order is returned. |
curl -X POST "https://app.centrapoint.co.za/api/v1/eft-orders" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
-H "Idempotency-Key: 5e7a9c1b-3d5f-4a7c-9e1b-3d5f7a9c1e2b" \
-H "Content-Type: application/json" \
-d '{
"amount": 1250,
"customer": {
"externalReference": "CRM-1001"
},
"description": "Order #1001",
"externalReference": "ORDER-1001",
"metadata": {
"cartId": "c_8841"
},
"notifyPayer": true
}'Returns 201 with the order. Either send the payer to payUrl (or set notifyPayer to email it), or show bankDetails, the exact amount and the paymentReference in your own app and upload the proof through the API.
List EFT orders#
/api/v1/eft-orders| Field | Type | Description |
|---|---|---|
externalReferenceoptional | string | Exact match. |
customerIdoptional | string | |
statusoptional | string | One of the statuses above. |
limitoptional | integer | 1–100, default 20. Newest first. |
curl -X GET "https://app.centrapoint.co.za/api/v1/eft-orders?status=awaiting_review" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY"Get an EFT order#
/api/v1/eft-orders/{id}curl -X GET "https://app.centrapoint.co.za/api/v1/eft-orders/cmg3e1f2t0009eft0001abcd" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY"Upload proof of payment#
/api/v1/eft-orders/{id}/proofSend multipart/form-data with one field named file. On success the order moves to awaiting_review, your staff are emailed, eft.proof_received is sent, and the updated order is returned (200).
curl -X POST "https://app.centrapoint.co.za/api/v1/eft-orders/cmg3e1f2t0009eft0001abcd/proof" -H "Authorization: Bearer $CENTRAPOINT_API_KEY" -F "[email protected];type=application/pdf"Proof rules#
- PDF, PNG or JPEG, detected from the file's bytes; the file name and declared content type are ignored. Anything else is rejected with
400. - At most 10 MB per file (
413otherwise) and 10 files per order. - Deduplicated: an identical file is not stored twice, and while the order is already awaiting review a repeat upload doesn't notify anyone again, so retries are safe. (This endpoint does not use the Idempotency-Key header.)
- Allowed while the order is
awaiting_payment,awaiting_revieworrejected, and not pastexpiresAt(unless already in review). After a rejection, uploading new proof moves the order back toawaiting_reviewand clearsrejectionReason. - A non-multipart body returns
415; a missingfilefield returns400.
Cancel an EFT order#
/api/v1/eft-orders/{id}/cancelCancels an unpaid order (awaiting_payment, awaiting_review or rejected) and its pending transaction, and sends eft.cancelled and payment.cancelled. No body is needed. Cancelling a cancelled order returns it unchanged; paid and expired orders return 400.
curl -X POST "https://app.centrapoint.co.za/api/v1/eft-orders/cmg3e1f2t0009eft0001abcd/cancel" \
-H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
-H "Idempotency-Key: 8b0d2f4a-6c8e-4a1c-b3e5-7f9a1c3e5b7d"Webhook events#
| Event | When | data |
|---|---|---|
eft.proof_received | Proof was uploaded (API or dashboard) | EFT order |
eft.rejected | Staff rejected the proof | EFT order (with rejectionReason) |
eft.expired | The order expired unpaid | EFT order |
eft.cancelled | The order was cancelled (API or dashboard) | EFT order |
payment.complete | Staff approved the order. data.eftOrderId identifies it. | Payment |
payment.cancelled | The order was cancelled or expired | Payment |
See Webhooks for signatures and delivery.
Errors#
| Status | error | When |
|---|---|---|
| 400 | invalid_request | Validation failed; notifyPayer without a customer email; Manual EFT not set up or not available on your plan; unsupported currency; customer not found; expiresAt out of range; open order with a different amount; bad or missing proof file; order not open or expired. |
| 403 | plan_restricted / plan_limit / account_restricted | No API access or payment links; monthly transaction limit reached (create); account read-only (writes). |
| 404 | not_found | No EFT order with this ID in your organisation. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different body (create, cancel). |
| 413 / 415 | invalid_request | Proof larger than 10 MB / body not multipart. |
| 401 / 429 / 500 | unauthorized / rate_limited / internal_error | See Errors |