# Single payments: integrate and test (https://docs.tryacme.com/guides/payment-lifecycle-and-statuses/single-payments)

> For AI agents: the documentation index is at [llms.txt](https://docs.tryacme.com/llms.txt), with every page in one file at [llms-full.txt](https://docs.tryacme.com/llms-full.txt). Each page is also available as Markdown by appending `.md` to its URL.

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](https://docs.tryacme.com/guides/payment-lifecycle-and-statuses/batch-payments).
For the statuses and webhooks, see
[Single payment flow](https://docs.tryacme.com/guides/payment-lifecycle-and-statuses#single-payment-flow).

## Create a payment [#create-a-payment]

Send [Create a Payment](https://docs.tryacme.com/reference/post-payments) (`POST /v1/payments`). Add an `Idempotency-Key`
header, so you can retry it safely (see [Idempotency](https://docs.tryacme.com/guides/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](https://docs.tryacme.com/guides/bank-payment-rules).

```json title="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](https://docs.tryacme.com/guides/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 [#response]

```json title="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](https://docs.tryacme.com/reference/get-payments-id) 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](https://docs.tryacme.com/guides/payment-lifecycle-and-statuses#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 [#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](https://docs.tryacme.com/guides/bank-payment-rules).

## Possible errors [#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](https://docs.tryacme.com/guides/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](https://docs.tryacme.com/reference/get-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](https://docs.tryacme.com/guides/scheduled-maintenance). | Send the payment again after the window ends. |
| `500` | An error at Acme. | Check [List Payments](https://docs.tryacme.com/reference/get-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 [#example-400-response]

```json title="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](https://docs.tryacme.com/reference/get-payments-id) for the latest one.

## Test mode [#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 [#steps]


#### Create a payment with your test key [#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 [#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 [#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 [#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 [#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](https://docs.tryacme.com/guides/payment-lifecycle-and-statuses/batch-payments#test-mode) 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](https://docs.tryacme.com/guides/special-test-mode-values).
