Single payments: integrate and test
Create a single payment, handle the answer and its errors, and test it in test mode.
A single payment goes to the bank over its API, one payment per request. To send many payments in one file instead, see Batch payments: integrate and test. For the statuses and webhooks, see Single payment flow.
Create a payment
Send Create a Payment (POST /v1/payments). Add an Idempotency-Key
header, so you can retry it safely (see Idempotency).
This example is a DBS Singapore FAST payment. The fields a payment needs depend on the bank and
the payment type: look them up in your bank's guide under
Bank payment rules.
{
"type": "FAST",
"amount": 150000,
"currency": "SGD",
"senderAccountId": "intacc_0H3BQY9BK1FCG",
"senderAccountCurrency": "SGD",
"customerReference": "INV-20260901-001",
"paymentDetails": "Payment for invoice INV-20260901-001",
"receiver": {
"name": "Chan Tai Man",
"bank": "OCBCSGSGXXX",
"bankAccountNumber": "7654321098"
}
}amountis in minor units:150000is 1,500.00 SGD. See Minor units format.senderAccountCurrencyis required when the sender account holds more than one currency in Acme. Test accounts usually hold two, so send it in test mode.customerReferenceis optional. Acme generates one when you leave it out.- For a
PAYNOWor other proxy payment, sendreceiver.proxyTypeandreceiver.proxyValueinstead of a bank account. Your bank's guide lists which fields each type takes.
Response
{
"id": "pymt_0J7Q2D4K9RXM3",
"type": "FAST",
"amount": 150000,
"currency": "SGD",
"customerReference": "INV-20260901-001",
"senderAccountId": "intacc_0H3BQY9BK1FCG",
"receiver": {
"name": "Chan Tai Man",
"bank": "OCBCSGSGXXX",
"bankAccountNumber": "7654321098"
},
"paymentDetails": "Payment for invoice INV-20260901-001",
"currencyExchange": { "fxContractId": null },
"senderAccountCurrency": "SGD",
"status": "COMPLETED",
"resultCode": null,
"bankReference": null,
"bankReferences": {},
"createdAt": "2026-09-01T02:15:32.418Z",
"updatedAt": "2026-09-01T02:15:33.102Z"
}statusis what the bank has said by the time Acme answers:PROCESSING,SUBMITTED,COMPLETEDorFAILED. Some banks answer at once, others later. Follow the payment with webhooks or Get a Payment until it isCOMPLETEDorFAILED.- A
200withFAILEDusually means the bank rejected the payment during the request.resultCodesays why, andunderlyingErrorMessageholds the bank's reason when it gives one. - With Acme maker-checker, the payment is
PENDING_APPROVALand is not sent to the bank yet. The create response has noreview; Get a Payment returns it.
Validation
Acme checks the payment before it sends it to the bank. Every payment must:
- Have an
amountand asenderAccountId. - Use a sender account of your organization, in the mode of your API key, that your API key may pay from.
- Give
senderAccountCurrency, one of the account's currencies, when the account holds more than one.
Always send type, currency and receiver too. DBS answers 400 when one is missing, but some
banks answer 500.
Each bank adds its own rules: which types it supports, which receiver fields are required or not allowed, and field lengths and characters. Your bank's guide lists them under Bank payment rules.
Possible errors
For the 4xx, 501 and 503 answers below, the payment was not sent to the bank. After a 500,
check before you retry.
| Status | What it means | What to do |
|---|---|---|
400 | The request failed validation. errorCode is INVALID_REQUEST_PARAMETER, and errorMessage names one field and the rule it broke. See the example below. | Fix the field and send the payment again, with a new Idempotency-Key. |
401 | Empty body. The API key is missing, wrong or disabled, or the request came from an IP address your organization does not allow. | Check the API key (a test key for test mode, a live key for live mode). If it is right, contact Acme. |
403 | The API key does not have the Payments permission, or may not pay from this sender account. | Contact Acme. |
409 | Empty body. The Idempotency-Key was used in the last hour with a different request, or the first request with this key is still running. | To retry, send the first request again, unchanged (the same body, byte for byte) and with the same key. If it is still running you get a 409 again: wait a few seconds and repeat. For a new payment, use a new key. |
429 | errorCode RATE_LIMIT_EXCEEDED. Your API key sent too many requests (Rate limits). With DBS or CIMB Singapore, it can also be the bank turning the request away: the errorMessage then starts The bank rate limited this request. | Wait for the Retry-After interval. After a bank 429, check List Payments before you retry; otherwise retry with the same Idempotency-Key. |
501 | DBS only: errorCode INVALID_OPERATION. DBS does not support this payment type or currency. Other banks answer 400 for an unsupported type. | Use a payment type and currency your bank's guide lists, with a new Idempotency-Key. |
503 | {"message": "Bank is under maintenance."}. Live mode only: the bank, or this payment type, has a maintenance window. See Scheduled maintenance. | Send the payment again after the window ends. |
500 | An error at Acme. | Check List Payments for a payment created at that time. If there is none, retry with a new Idempotency-Key. If it fails again, contact Acme with the time of the request. |
Example 400 response
{
"errorCode": "INVALID_REQUEST_PARAMETER",
"errorMessage": "receiver.bankAccountNumber: must be provided"
}- A single payment reports one failing field at a time. Fix it and send again to see the next one.
- A
400for the sender account's currency has a fixed message, for exampleSender account has multiple currencies. Please specify one to use in senderAccountCurrency.
If you get no response at all (a timeout), send the same request again (the same body, byte for byte)
with the same Idempotency-Key within an hour. You get the original answer, and no second payment is
made. Acme saves every answer except 401, 403, 429 and a maintenance 503, so a retry with the
same key returns the saved answer, and its status is the one at that time: call
Get a Payment for the latest one.
Test mode
Use your test API key to try your integration before going live. In test mode, Acme checks the
payment with the same rules as in live mode, then simulates the bank instead of sending it. The
exception is the banks that cannot be tested yet, listed below. Test mode
has no maintenance windows, so it never answers 503.
Steps
Create a payment with your test key
Send the request above with your test API key and a test sender account. Include
senderAccountCurrency: test accounts usually hold two currencies.
Make it fail
Send another payment with receiver.bankAccountNumber 000000000. The payment fails at once, as if
the bank rejected it.
Check the webhooks and statuses
Compare what you receive with the table below for your bank, then check your system updated each payment.
Test account numbers
The values are matched on receiver.bankAccountNumber only, not on receiver.iban or
receiver.proxyValue. Any receiver bank works.
receiver.bankAccountNumber | What happens on most banks |
|---|---|
000000000 or 000000001 | The response is FAILED, and Acme sends payments.failed. |
| Any other number | The payment is COMPLETED, and Acme sends payments.succeeded. |
By bank
| Bank and market | Test behaviour |
|---|---|
| CIMB Singapore, HSBC Hong Kong, HSBC Malaysia, RHB Malaysia, Standard Chartered (Singapore, Great Britain, Hong Kong, UAE), UOB Malaysia | As in the table above. |
| DBS Singapore | 000000000: the response is FAILED. 000000001: the response is SUBMITTED, then the payment is FAILED straight away. Any other number: the response is SUBMITTED, then COMPLETED. resultCode stays empty. |
| Standard Chartered UAE | A UAE IBAN never matches the test values. To make a payment fail, send 000000000 as the account number with receiver.bank. |
| OCBC Singapore | Only PAYNOW by proxy, which takes no bankAccountNumber: every test payment is COMPLETED. |
| Zand UAE, Banco Azteca Mexico | The test values are ignored: every test payment is COMPLETED. |
| Citibank Singapore, Citibank Malaysia, UOB Singapore | Single payments cannot be tested yet: every test payment is FAILED, with no webhook. Test with a batch payment instead. |
- A proxy payment fails only if it also carries a test
bankAccountNumber, which some banks' rules do not allow. - For other test values, such as for direct debit and hosted payments, see Test mode values.