Payment product guide
Balance inquiry, transfers, transaction inquiry, SWIFT messages, fiat 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
- Fiat accounts: Open fiat collection accounts, and query the opening progress and collection details
- 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 |
| Provider KYC | GET /openapi/v1/customers/provider-kyc/requirements | Get KYC requirements |
| Provider KYC | POST /openapi/v1/customers/provider-kyc | Submit KYB |
| Provider KYC | GET /openapi/v1/customers/provider-kyc | Get KYC status |
| Provider KYC | GET /openapi/v1/customers/provider-kyc/rfis | List RFIs |
| Provider KYC | GET /openapi/v1/customers/provider-kyc/rfis/{rfiId} | Get an RFI |
| Provider KYC | POST /openapi/v1/customers/provider-kyc/rfis/{rfiId}/questions/{questionId}/idv-session | Create an IDV session |
| Provider KYC | POST /openapi/v1/customers/provider-kyc/rfis/{rfiId}/answers | Submit RFI answers |
| Fiat account | GET /openapi/v1/fiat-accounts/opening-options | List opening options |
| Fiat account | POST /openapi/v1/fiat-accounts | Create a fiat account |
| Fiat account | GET /openapi/v1/fiat-accounts/{accountId} | Retrieve an account |
| Webhook | HTTPS URL configured by the merchant | Receives the credit notification va.credit.create |
| Webhook | HTTPS URL configured by the merchant | LEGEND fiat account terminal events fiat.account.activated, fiat.account.failed, and fiat.account.closed. The payload carries only a few fields. Call Retrieve an account for the payment details |
| Webhook | HTTPS URL that you configure | Provider KYC status provider.kyc.submitted, provider.kyc.approved, provider.kyc.rejected, and provider.kyc.action_required. See Provider KYC and account opening |
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. GLDB create fiat account, submit KYB, and submit RFI answers use multipart/form-data | Yes, for POST requests |
Idempotency-Key | Unique request identifier that prevents duplicate operations (UUID recommended). For the rules, see Idempotency | Yes. Required for the three transfer POST endpoints, LEGEND create fiat account, create digital wallet, generate a deposit address, submit KYB, and submit RFI answers. Do not send it for GLDB create fiat account |
X-Channel-Provider | Provider identifier, such as GLDB | Yes |
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:
- The three transfer
POSTendpoints, LEGEND create fiat account, create digital wallet, and generate a deposit address must includeIdempotency-Keyin the request header - GLDB create fiat account does not support
Idempotency-Key. A request with it is rejected. See Fiat accounts - Submit KYB and submit RFI answers also require
Idempotency-Key, but a repeat request is not rejected. If the provider already accepted it, the current status is returned. See Provider KYC and account opening - 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
Each request above must use a unique Idempotency-Key. Never reuse the same key for different operations.
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, do not resubmit. For a transfer, query the result with referenceNo. For account or wallet creation, query by the returned ID. For address generation, query the address list.
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.
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
| 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"
}
}
}Fiat accounts
Fiat accounts use the X-Channel-Provider request header to select the provider. LEGEND and GLDB are supported.
Before a LEGEND account is opened, the customer must complete KYC at the provider and get a providerCustomerId. For the full flow from KYB to account opening with examples, see Provider KYC and account opening.
The two providers differ in request format and opening flow:
| Item | LEGEND | GLDB |
|---|---|---|
| Create request format | application/json | multipart/form-data, with the opening documents uploaded in the same request |
Idempotency-Key | Required | Do not send it. If you send it, fiat.account.idempotency_key.unsupported is returned |
| Provider-specific fields | providerCustomerId (required), bankCountry, alias, openingOptionId | accountName, contactName, contactEmail, contactPhone, companyRegistrationDoc (all required), accountOpeningProof, otherDocuments (optional) |
| Opening flow | Synchronous or asynchronous, decided by the provider | Manual review. Create returns PENDING. After approval, the status is ACTIVE |
| List opening options | Supported | Not supported. openapi.channel.unsupported is returned |
| Retrieve an account | providerCustomerId is required | Do not send providerCustomerId. accountId is the application number |
| Status notification | A Webhook is sent for terminal statuses | No Webhook. Poll the account detail |
currencyis required for both providers. It accepts only one currency.- If you send a field that belongs to the other provider,
fiat.account.field.unsupportedis returned. - If a field that this provider requires is missing,
fiat.account.field.requiredis returned.
Some providers open the account synchronously, and the create response is the account detail. Some providers open the account asynchronously, and create returns only PENDING. The account can receive funds only after it is ACTIVE.
List opening options (optional, LEGEND only) → Create a fiat account (required) → Retrieve an account (conditional)| Step | Endpoint | Required | Description |
|---|---|---|---|
| 1 | List opening options | Optional, LEGEND only | Call it when you need to choose the opening route or confirm the fees in advance. If you do not call it, the provider chooses the route. |
| 2 | Create a fiat account | Required | For LEGEND, submit JSON. If you chose a route in step 1, send back the returned openingOptionId. On synchronous success, the response is already the standard account. For GLDB, submit multipart/form-data with the opening documents. The response is PENDING. |
| 3 | Retrieve an account | Conditional | If create returns PENDING, poll until a terminal status. For GLDB, you must poll. If create returns fiat.account.channel.detail_unavailable, query with the account ID in the message. Do not open the account again. If create already returned ACTIVE, you do not need to query. |
PENDING means the request is accepted but the account cannot receive funds yet. ACTIVE includes at least an account number or an IBAN.
Integration sequence examples
Examples for other providers will be added to this section.
Reference
For charge bearer, purpose codes, transaction type codes, and country codes, 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.
Provider KYC and Account Opening
Complete KYB, RFI answers, and fiat account opening for a business customer through the LEGEND provider. Each step has request and response examples.