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.

Request
{
  "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"
  }
}
  • amount is in minor units: 150000 is 1,500.00 SGD. See Minor units format.
  • senderAccountCurrency is required when the sender account holds more than one currency in Acme. Test accounts usually hold two, so send it in test mode.
  • customerReference is optional. Acme generates one when you leave it out.
  • For a PAYNOW or other proxy payment, send receiver.proxyType and receiver.proxyValue instead of a bank account. Your bank's guide lists which fields each type takes.

Response

200 OK
{
  "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"
}
  • status is what the bank has said by the time Acme answers: PROCESSING, SUBMITTED, COMPLETED or FAILED. Some banks answer at once, others later. Follow the payment with webhooks or Get a Payment until it is COMPLETED or FAILED.
  • A 200 with FAILED usually means the bank rejected the payment during the request. resultCode says why, and underlyingErrorMessage holds the bank's reason when it gives one.
  • With Acme maker-checker, the payment is PENDING_APPROVAL and is not sent to the bank yet. The create response has no review; Get a Payment returns it.

Validation

Acme checks the payment before it sends it to the bank. Every payment must:

  • Have an amount and a senderAccountId.
  • 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.

StatusWhat it meansWhat to do
400The 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.
401Empty 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.
403The API key does not have the Payments permission, or may not pay from this sender account.Contact Acme.
409Empty 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.
429errorCode 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.
501DBS 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.
500An 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

400 Bad Request
{
  "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 400 for the sender account's currency has a fixed message, for example Sender 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.bankAccountNumberWhat happens on most banks
000000000 or 000000001The response is FAILED, and Acme sends payments.failed.
Any other numberThe payment is COMPLETED, and Acme sends payments.succeeded.

By bank

Bank and marketTest behaviour
CIMB Singapore, HSBC Hong Kong, HSBC Malaysia, RHB Malaysia, Standard Chartered (Singapore, Great Britain, Hong Kong, UAE), UOB MalaysiaAs in the table above.
DBS Singapore000000000: 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 UAEA UAE IBAN never matches the test values. To make a payment fail, send 000000000 as the account number with receiver.bank.
OCBC SingaporeOnly PAYNOW by proxy, which takes no bankAccountNumber: every test payment is COMPLETED.
Zand UAE, Banco Azteca MexicoThe test values are ignored: every test payment is COMPLETED.
Citibank Singapore, Citibank Malaysia, UOB SingaporeSingle 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.

On this page