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

ModuleEndpointDescription
AccountGET /openapi/v1/payments/balanceBalance inquiry
TransferPOST /openapi/v1/payments/transfer/intra-bankIntra-bank transfer
TransferGET /openapi/v1/payments/transfer/intra-bank/{referenceNo}Intra-bank transfer result inquiry
TransferPOST /openapi/v1/payments/transfer/mepsMEPS+ transfer
TransferGET /openapi/v1/payments/transfer/meps/{referenceNo}MEPS+ transfer result inquiry
TransferPOST /openapi/v1/payments/transfer/swiftCross-border remittance
TransferGET /openapi/v1/payments/transfer/swift/{referenceNo}Cross-border remittance result inquiry
TransactionGET /openapi/v1/payments/transaction/historyTransaction history inquiry
TransactionGET /openapi/v1/payments/message/mtmxMT/MX message inquiry
FeePOST /openapi/v1/payments/fees/estimateEstimate the transfer fee
Virtual accountPOST /openapi/v1/wallet/virtual-accountsApply for a virtual account
Virtual accountPUT /openapi/v1/wallet/virtual-account-requests/{requestNo}Update a virtual account request
Virtual accountGET /openapi/v1/wallet/virtual-account-requests/{requestNo}Retrieve a virtual account request
Virtual accountGET /openapi/v1/wallet/virtual-account-requestsList virtual account requests
Virtual accountGET /openapi/v1/wallet/virtual-accountsList virtual accounts
Virtual accountGET /openapi/v1/wallet/virtual-accounts/{virtualAccountNo}Retrieve a virtual account
WebhookHTTPS URL configured by the merchantReceives 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.

HeaderDescriptionRequired
AuthorizationBearer {accessToken}Yes
Content-Typeapplication/jsonYes, for POST requests
Idempotency-KeyUnique request identifier that prevents duplicate operations (UUID recommended). For the rules, see IdempotencyYes, for the three transfer POST endpoints
X-Channel-ProviderProvider identifier, such as GLDBYes. 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:

FieldDescriptionTypeExample
providerStatusCodeProvider response codeString000000
providerMessageProvider response messageStringok
providerRequestSerialNoProvider request serial numberString795883203712778240
providerResponseSerialNoProvider response serial numberString785957885476737024
providerTimestampProvider request timestampString2023-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

CodeDescription
000000Success
100019Version number is empty
100020Provider serial number is empty
100021Timestamp is empty
101119Timestamp mismatch (more than 2 minutes)
100111Field length exceeds the limit
100017Endpoint does not exist or is in an abnormal status
100004Signature verification failed
100067Payload decryption failed
100098Developer application is in an abnormal status
100060Developer certificate not found
100099Error sending to the downstream system
999999Unknown error
199999Downstream system returned an error

Transaction status

StatusDescription
SUCCESSSucceeded
PROCESSINGProcessing
SENT_TO_BANKSent to the bank
FAILEDFailed

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 POST request, you must provide Idempotency-Key in the HTTP header
  • Stabo processes requests that use the same Idempotency-Key only once
  • The client generates the Idempotency-Key (UUID recommended). It is valid for 24 hours
  • GET requests 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

  • remarks accepts 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 pass accountNumber=poboAccountNo to specify the POBO payout account explicitly. If you omit accountNumber, the system matches the POBO payout account automatically by referenceNo.

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=10000000003

MEPS+ 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, and remarks accept 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 pass accountNumber=poboAccountNo to specify the POBO payout account explicitly. If you omit accountNumber, the system matches the POBO payout account automatically by referenceNo.

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, and remarks accept 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 pass accountNumber=poboAccountNo to specify the POBO payout account explicitly. If you omit accountNumber, the system matches the POBO payout account automatically by referenceNo.

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&currency=SGD&startDate=20240213&endDate=20240426&currentPage=1&pageSize=20

A 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=OUTGOING

SWIFT 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.
  • contactPhone uses 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=20

Credit 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

FieldTypeRequiredDescription
transactionReferenceStringYTransaction reference number
tradeDateStringYTransaction date, in the bank's original format yyyyMMdd, such as 20260528
tradeTimeStringYTransaction time, in the bank's original format HHmmssSSS. For example, 101135517 means 10:11:35.517
valueDateStringYValue date, in the bank's original format yyyyMMdd, such as 20260528
currencyStringYCurrency
amountStringYAmount
senderAccountStringYPayer account number
senderNameStringYPayer name
senderBankCodeStringNPayer bank code
receiverAccountStringYBeneficiary account number
receiverNameStringYBeneficiary name
receiverBankCodeStringNBeneficiary bank code
remarksStringNRemarks
directionStringYDirection (INCOMING/OUTGOING)
paymentReferenceStringNPayment reference number
sourceStringYSource (SWIFT/SWIFTMX/FAST/MEPS/INTERNAL)
eventReferenceStringYEvent reference number
accountNumberStringYAccount number
senderBankNameStringNPayer bank name
masterAccountNoStringNMaster 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 this page