Payment product guide
Balance inquiry, transfers, transaction inquiry, SWIFT messages, virtual accounts, and credit notifications. See the API reference for endpoint details.
Overview
The Stabo Payment OpenAPI is for corporate customers. With this API, you can:
- Account management: Query account balance and status in real time
- Fund transfers: Make intra-bank transfers, MEPS+ local transfers, and SWIFT cross-border remittances
- Transaction inquiry: Query transaction history and transfer status
- Virtual account management: Apply for and manage virtual sub-accounts (Virtual Account)
- Message retrieval: Retrieve the original MT/MX SWIFT messages
- Real-time notifications: Receive credit and other event notifications through Webhooks
Version: v1.7
For onboarding, environment information, and authentication, see Quickstart and Authentication. For the protocol and the unified response format, see Conventions.
API overview
| Module | Endpoint | Description |
|---|---|---|
| Account | GET /openapi/v1/payments/balance | Balance inquiry |
| Transfer | POST /openapi/v1/payments/transfer/intra-bank | Intra-bank transfer |
| Transfer | GET /openapi/v1/payments/transfer/intra-bank/{referenceNo} | Intra-bank transfer result inquiry |
| Transfer | POST /openapi/v1/payments/transfer/meps | MEPS+ transfer |
| Transfer | GET /openapi/v1/payments/transfer/meps/{referenceNo} | MEPS+ transfer result inquiry |
| Transfer | POST /openapi/v1/payments/transfer/swift | Cross-border remittance |
| Transfer | GET /openapi/v1/payments/transfer/swift/{referenceNo} | Cross-border remittance result inquiry |
| Transaction | GET /openapi/v1/payments/transaction/history | Transaction history inquiry |
| Transaction | GET /openapi/v1/payments/message/mtmx | MT/MX message inquiry |
| Fee | POST /openapi/v1/payments/fees/estimate | Estimate the transfer fee |
| Virtual account | POST /openapi/v1/wallet/virtual-accounts | Apply for a virtual account |
| Virtual account | PUT /openapi/v1/wallet/virtual-account-requests/{requestNo} | Update a virtual account request |
| Virtual account | GET /openapi/v1/wallet/virtual-account-requests/{requestNo} | Retrieve a virtual account request |
| Virtual account | GET /openapi/v1/wallet/virtual-account-requests | List virtual account requests |
| Virtual account | GET /openapi/v1/wallet/virtual-accounts | List virtual accounts |
| Virtual account | GET /openapi/v1/wallet/virtual-accounts/{virtualAccountNo} | Retrieve a virtual account |
| Webhook | HTTPS URL configured by the merchant | Receives the credit notification va.credit.create |
For the parameters, responses, and online testing of each endpoint, see the API reference.
Request headers
In addition to the request headers in Conventions, Payment endpoints require X-Channel-Provider.
| Header | Description | Required |
|---|---|---|
Authorization | Bearer {accessToken} | Yes |
Content-Type | application/json | Yes, for POST requests |
Idempotency-Key | Unique request identifier that prevents duplicate operations (UUID recommended). For the rules, see Idempotency | Yes, for the three transfer POST endpoints |
X-Channel-Provider | Provider identifier, such as GLDB | Yes. Virtual account endpoints default to GLDB when it is omitted |
Common data fields
The data object of every business response contains the following common fields:
| Field | Description | Type | Example |
|---|---|---|---|
providerStatusCode | Provider response code | String | 000000 |
providerMessage | Provider response message | String | ok |
providerRequestSerialNo | Provider request serial number | String | 795883203712778240 |
providerResponseSerialNo | Provider response serial number | String | 785957885476737024 |
providerTimestamp | Provider request timestamp | String | 2023-05-17 21:03:48 |
Error responses
Use the HTTP status code to determine whether a request succeeded. For the rules, see Errors. When the provider returns an error, the HTTP status code is 400, code in the response body is 500, and data contains the provider* fields returned by the provider:
{
"code": 500,
"message": "Operation failed",
"traceId": "trace-uuid-string",
"data": {
"providerStatusCode": "100019",
"providerMessage": "Version number is empty",
"providerRequestSerialNo": "795883203712778240",
"providerResponseSerialNo": "785957885476737024",
"providerTimestamp": "2023-05-17 21:03:48"
}
}Provider response codes
The provider determines the values of providerStatusCode.
GLDB response codes
| Code | Description |
|---|---|
000000 | Success |
100019 | Version number is empty |
100020 | Provider serial number is empty |
100021 | Timestamp is empty |
101119 | Timestamp mismatch (more than 2 minutes) |
100111 | Field length exceeds the limit |
100017 | Endpoint does not exist or is in an abnormal status |
100004 | Signature verification failed |
100067 | Payload decryption failed |
100098 | Developer application is in an abnormal status |
100060 | Developer certificate not found |
100099 | Error sending to the downstream system |
999999 | Unknown error |
199999 | Downstream system returned an error |
Transaction status
| Status | Description |
|---|---|
SUCCESS | Succeeded |
PROCESSING | Processing |
SENT_TO_BANK | Sent to the bank |
FAILED | Failed |
Idempotency
The Stabo API supports idempotency to prevent duplicate operations (such as duplicate debits) caused by network interruptions or similar issues.
How it works:
- When you send a transfer
POSTrequest, you must provideIdempotency-Keyin the HTTP header - Stabo processes requests that use the same
Idempotency-Keyonly once - The client generates the
Idempotency-Key(UUID recommended). It is valid for 24 hours GETrequests are idempotent by nature and do not need this header
Use a unique Idempotency-Key for each transfer. Never reuse the same key for different transactions.
If you reuse an Idempotency-Key within its validity period, the request is rejected (HTTP 400, message is Duplicate request detected). The result of the first request is not returned. A duplicate request is also rejected while the first request is still processing. The key is taken as soon as the request is accepted, and it is not released even if the first request fails. If the request times out or you receive no response, call the result inquiry endpoint with referenceNo to confirm the status. Do not resubmit the request.
Balance inquiry
Query the available balance of an account. accountNumber and currency are required.
curl -G "https://api.test.stabofx.com/openapi/v1/payments/balance" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: GLDB" \
--data-urlencode "accountNumber=10000000001" \
--data-urlencode "currency=USD"Response example:
{
"code": 200,
"message": "OK",
"traceId": "a1b2c3d4-e5f6-7890",
"data": {
"providerStatusCode": "000000",
"providerMessage": "ok",
"providerRequestSerialNo": "795883203712778240",
"providerResponseSerialNo": "785957885476737024",
"providerTimestamp": "2023-05-17 21:03:48",
"balance": "1000000.00",
"currency": "USD",
"status": "ACTIVE"
}
}Values of status: ACTIVE / CLOSED / INACTIVE / DORMANT.
Intra-bank transfer
Transfer funds between accounts within the bank. All currencies are supported. Use it to move funds between different accounts at the same bank.
curl -X POST "https://api.test.stabofx.com/openapi/v1/payments/transfer/intra-bank" \
-H "Authorization: Bearer {accessToken}" \
-H "Content-Type: application/json" \
-H "X-Channel-Provider: GLDB" \
-H "Idempotency-Key: STABO20231121001" \
-d '{
"referenceNo": "STABO20231121001",
"fromAccount": "10000000001",
"beneficiaryAccount": "10000000002",
"currency": "SGD",
"amount": "200.00",
"remarks": "Payment for services"
}'Response example:
{
"code": 200,
"message": "OK",
"traceId": "a1b2c3d4-e5f6-7890",
"data": {
"providerStatusCode": "000000",
"providerMessage": "ok",
"providerRequestSerialNo": "795883203712778240",
"providerResponseSerialNo": "785957885476737024",
"providerTimestamp": "2023-05-17 21:03:48",
"channelSerialNo": "742465990302056448",
"status": "SUCCESS",
"coreSerialNo": "FT23131QQQ2P",
"poboAccountNo": "10000000003",
"poboAccountName": "EXAMPLE PTE. LTD."
}
}Notes
remarksaccepts only letters, digits, spaces, and the special characters/ - ? : ( ) . , ' +- If the response status is
PROCESSING, call the result inquiry endpoint after 5 minutes to confirm the final status - For orders paid out through POBO, the response returns
poboAccountNo/poboAccountName. When you call the result inquiry endpoint, we recommend that you passaccountNumber=poboAccountNoto specify the POBO payout account explicitly. If you omitaccountNumber, the system matches the POBO payout account automatically byreferenceNo.
Transfer result inquiry
Each of the three transfer types has a result inquiry endpoint, with the same parameters and response. If a transfer returns PROCESSING, call the result inquiry endpoint to get the final result.
# Standard orders / POBO payout orders
GET /openapi/v1/payments/transfer/intra-bank/STABO20231121001
# For POBO payout orders, you can also specify the payout account explicitly
GET /openapi/v1/payments/transfer/intra-bank/STABO20231121001?accountNumber=10000000003MEPS+ transfer
Make real-time local transfers in Singapore through MEPS+ (MAS Electronic Payment System).
Features:
- SGD only
- Real-time settlement
- Suitable for high-value local transfers
Request example:
{
"referenceNo": "STABO20231121002",
"fromAccount": "10000000001",
"beneficiaryAccount": "10000000004",
"beneficiaryName": "Example Company",
"beneficiaryAddress": "1 Example Road, Singapore 000000",
"beneficiaryCountryCode": "SG",
"currency": "SGD",
"amount": "100.00",
"beneficiaryBankCode": "EXMPSGS0XXX",
"beneficiaryBankName": "EXAMPLE BANK LTD",
"purposeCode": "BEXP",
"chargeBearer": "SHA"
}For the values of purposeCode and chargeBearer, see the Reference.
Notes
beneficiaryName,beneficiaryAddress, andremarksaccept only letters, digits, spaces, and the special characters/ - ? : ( ) . , ' +- If the beneficiary bank is not a MEPS+ participant, provide intermediary bank information
- For orders paid out through POBO, the response returns
poboAccountNo/poboAccountName. When you call the result inquiry endpoint, we recommend that you passaccountNumber=poboAccountNoto specify the POBO payout account explicitly. If you omitaccountNumber, the system matches the POBO payout account automatically byreferenceNo.
Cross-border remittance
Make cross-border remittances through the SWIFT network. Note: The GLDB provider does not support cross-border remittances in SGD.
Request example:
{
"referenceNo": "STABO20231121003",
"fromAccount": "10000000001",
"beneficiaryAccount": "10000000005",
"beneficiaryName": "Example Company",
"beneficiaryAddress": "1 Example Road",
"beneficiaryCountryCode": "SG",
"beneficiaryCountryName": "Singapore",
"currency": "USD",
"amount": "1001.64",
"beneficiaryBankCode": "EXMPSGS0XXX",
"beneficiaryBankName": "EXAMPLE BANK LTD",
"chargeBearer": "SHA",
"purposeCode": "BEXP",
"remarks": "open transfer test"
}Notes (GLDB provider)
- SGD is not supported
- If a BIC code has fewer than 11 characters, pad it with
X beneficiaryName,beneficiaryAddress, andremarksaccept only letters, digits, spaces, and the special characters/ - ? : ( ) . , ' +- For orders paid out through POBO, the response returns
poboAccountNo/poboAccountName. When you call the result inquiry endpoint, we recommend that you passaccountNumber=poboAccountNoto specify the POBO payout account explicitly. If you omitaccountNumber, the system matches the POBO payout account automatically byreferenceNo.
Transaction history inquiry
Query the transaction history of an account, with pagination. Use it for reconciliation and financial reporting.
GET /openapi/v1/payments/transaction/history?accountNumber=10000000001¤cy=SGD&startDate=20240213&endDate=20240426¤tPage=1&pageSize=20A positive amount is a credit (incoming), and a negative amount is a debit (outgoing). The maximum pageSize is 100. For the values of transactionType, see Transaction type codes.
Estimate the transfer fee
Calculate the estimated fee based on the transfer type and amount. The request body is JSON.
Request example:
{
"transferType": "SWIFT",
"amount": "100.00",
"currency": "USD",
"usdEquivalentAmount": "100.00",
"chargeOption": "SHA"
}fee has two decimal places and is rounded up. For example, 1.221 becomes 1.23. The fee currency is SGD for INTRA_BANK, MEPS, and SWIFT SGD, and USD for non-SGD SWIFT. The current fee estimation logic does not depend on chargeOption.
MT/MX message inquiry
Query the original MT/MX SWIFT message associated with a transfer. Use it for compliance audits and troubleshooting. paymentReference comes from the transfer result inquiry endpoint.
GET /openapi/v1/payments/message/mtmx?paymentReference=SGL24241GG0LJHCB&direction=OUTGOINGSWIFT message examples
The following examples show the content of the messageContent field in different scenarios. The institutions, account numbers, and reference numbers in the examples are placeholders.
Virtual accounts
Apply for virtual sub-accounts for MSO (Money Service Operator) customers. Payment institutions use this to open separate virtual accounts for their downstream merchants.
Flow: Apply → Bank review → Query the request status → After the request succeeds, get the virtual account number from the request details → Query the virtual account.
- The apply and update request endpoints use
multipart/form-data, not JSON. - After you submit a request, the bank must review it. Use the inquiry endpoints to track the request status.
- After the request succeeds, the virtual account number is returned in the request details.
- You can update only requests whose status is
PENDING. - For files, the last submission takes effect. Resubmit all files with every submission. Newly uploaded files overwrite the existing files.
contactPhoneuses the E.164 format, such as+6591234567, with no spaces.- These endpoints do not validate
Idempotency-Key. - For request statuses and virtual account statuses, see the Reference.
Request example (apply):
POST /openapi/v1/wallet/virtual-accounts
Content-Type: multipart/form-data
accountName=Example Trading Pte Ltd
currency=SGD,USD
contactName=Example Contact
contactEmail=contact@example.com
contactPhone=+6500000000
companyRegistrationDoc=<binary file data>
otherDocuments=<binary file data>Response example:
{
"code": 200,
"message": "OK",
"traceId": "6a3ae549-32dd-469a-a6b8-4e213c070930",
"data": {
"requestNo": "SA0000000001",
"status": "PENDING"
}
}List endpoints paginate with pageNum (default 1) and pageSize (default 10, maximum 1000). For example:
GET /openapi/v1/wallet/virtual-accounts?status=ACTIVE&pageNum=1&pageSize=20Credit notification (va.credit.create)
When an account receives incoming funds, Stabo sends a va.credit.create event through a Webhook.
The notification mechanism, retry policy, request headers, event object structure, and signature verification are the same as for other products. See Webhooks. This section describes only the payload of this event. Webhook-Event-Type is va.credit.create.
Best practice: When you receive a Webhook notification, return a 2xx response first and then process the business logic asynchronously. This prevents duplicate deliveries caused by processing timeouts.
data.object fields
| Field | Type | Required | Description |
|---|---|---|---|
transactionReference | String | Y | Transaction reference number |
tradeDate | String | Y | Transaction date, in the bank's original format yyyyMMdd, such as 20260528 |
tradeTime | String | Y | Transaction time, in the bank's original format HHmmssSSS. For example, 101135517 means 10:11:35.517 |
valueDate | String | Y | Value date, in the bank's original format yyyyMMdd, such as 20260528 |
currency | String | Y | Currency |
amount | String | Y | Amount |
senderAccount | String | Y | Payer account number |
senderName | String | Y | Payer name |
senderBankCode | String | N | Payer bank code |
receiverAccount | String | Y | Beneficiary account number |
receiverName | String | Y | Beneficiary name |
receiverBankCode | String | N | Beneficiary bank code |
remarks | String | N | Remarks |
direction | String | Y | Direction (INCOMING/OUTGOING) |
paymentReference | String | N | Payment reference number |
source | String | Y | Source (SWIFT/SWIFTMX/FAST/MEPS/INTERNAL) |
eventReference | String | Y | Event reference number |
accountNumber | String | Y | Account number |
senderBankName | String | N | Payer bank name |
masterAccountNo | String | N | Master account number. Applies only to multi-currency accounts; single-currency messages do not include this field |
Example payload
{
"id": "evt_1234567890",
"object": "event",
"type": "va.credit.create",
"created": 1712345678,
"data": {
"object": {
"transactionReference": "TXN0000000001",
"tradeDate": "20260528",
"tradeTime": "101135517",
"valueDate": "20260528",
"currency": "USD",
"amount": "20600.00",
"senderAccount": "123456789",
"senderName": "EXAMPLE SENDER PTE LTD",
"senderBankCode": "EXMPHKHHXXX",
"receiverAccount": "10000000001",
"receiverName": "EXAMPLE RECEIVER PTE LTD",
"receiverBankCode": "EXMBSGSGXXX",
"remarks": "INVOICE-0001",
"direction": "INCOMING",
"paymentReference": "SGL0000000000000",
"source": "SWIFTMX",
"eventReference": "00000000-0000-4000-8000-000000000001",
"accountNumber": "10000000001",
"senderBankName": "EXAMPLE BANK LIMITED",
"masterAccountNo": "10000000000"
}
}
}Reference
For charge bearer, purpose codes, transaction type codes, country codes, virtual account request statuses, and virtual account statuses, see the Payment reference.
FAQ and support
Q: How do I make sure a transfer is not executed twice?
A: Use a unique Idempotency-Key for each transfer request. Each key is processed only once, and resubmitting it within the validity period is rejected. After a network timeout or a missing response, do not resubmit; call the result inquiry endpoint with referenceNo to confirm the status. See Idempotency.
For general questions and how to contact technical support, see the FAQ. For how error responses work, see Errors. Include the traceId when you contact technical support.
On-Ramp
Buy USDT with Mexican pesos (MXN). Receive account opening and queries, quotes, order creation and queries, order statuses, and Webhook events. For endpoint details, see the API Reference.
Payment reference
Charge bearer, purpose codes, transaction type codes, country codes, and virtual account statuses.