Singapay Home Page
Logo Icon
  1. Disbursement
  2. Disbursements Transfer

Information

MethodPathFormatAuthentication
POST/api/v2.0/disbursement/transferjsonOAuth 2.0 with Access Token

Request Details

Headers Structure

FieldValueTypeMandatoryLengthDescriptionExample
X-PARTNER-IDapi_keyAlphanumericMandatoryAPI key obtained from the merchant dashboard.b3ed7d4b-a96c-6c08-b3c7-12c3124242d9
Acceptapplication/jsonAlphabeticMandatorySpecifies JSON as the expected response format.application/json
AuthorizationBearer {bearerToken}AlphanumericMandatoryBearer token obtained from the get access token endpoint.Bearer eyJ0eXAiOiJKV1…
X-SignatureHMAC-SHA512 SignatureAlphanumericMandatoryHMAC-SHA512 signature for request authentication. See signature generation guide below.a1b2c3d4e5f6…
X-TimestampUnix Timestamp (seconds)NumericMandatoryRequest timestamp in Unix seconds format1714618220

Body Structure

FieldTypeMandatoryLengthDescriptionExample
account_idStringMandatoryMax: 50Account ID01K8N5ZNJH8MPGAK9QM080CXW8
reference_numberStringMandatoryMax: 32Unique identifier for the transaction. Must be unique per merchant.“2736437254634364534342”
amountNumericMandatoryMin: 10000, Max: merchant limitThe transaction amount.50000
bank_codeAlphanumericMandatory3-8Bank Number Code (3 digit) or Bank Swift Code (8 Character Length)BRINIDJA
bank_account_numberNumericMandatory6-20 digitsThe beneficiary’s bank account number.“521398319083210”
notesStringOptionalMax: 50Additional notes for the transaction.“bayar cicilan”

Body Example

{
    "account_id": "01K8N5ZNJH8MPGAK9QM080CXW8",
    "reference_number" : "5176770190020260101114",
    "bank_account_number": "8330432404",
    "amount": 10000,
    "bank_code": "014",
    "notes": "payroll bulan ini"
}

Response Details

Response Structure

FieldTypeMandatoryDescriptionExample
response_codeStringMandatory5Response code. Please check appendix 01
response_messageStringMandatory50Response message. Please check appendix 01
dataObjectMandatoryResponse payload object-
> transaction_idStringMandatorySystem-generated transaction ID“1512220251105174015668”
> reference_numberStringMandatoryMerchant’s unique reference number“123456789123”
> transaction_statusObjectMandatoryTransaction status. I.e: 00 (Success), 01 (Initiated) , etc, Please check appendix 02
>> codeNumericMandatory-I.e: 01, 02, etc
>> descStringMandatory-I.e: Success, Initiated, etc
> post_timestampStringMandatoryTransaction creation time (Unix milliseconds)“1762339215000”
> processed_timestampStringConditionalProcessing completion time (Unix ms, empty if failed)“1762339215672”
> bankObjectMandatoryBeneficiary bank information-
>> codeStringMandatoryBank Swift or Number code (e.g., 002, BRINIDJA)BRINIDJA
>> nameStringMandatoryBank code (e.g., BRI, BNI, DANAMON)“BRI”
>> account_nameStringConditionalBeneficiary account holder name (null if inquiry failed)“Yayasan Wahyudin Tbk”
>> account_numberStringMandatoryBeneficiary bank account number“521398319083210”
> gross_amountObjectMandatoryTotal amount including fees-
>> currencyStringMandatoryCurrency code (ISO 4217)“IDR”
>> valueStringMandatoryGross amount value“50000”
> feeObjectMandatoryTransfer fee details-
>> nameStringMandatoryFee description“Transfer Fee”
>> currencyStringMandatoryCurrency code (ISO 4217)“IDR”
>> valueStringMandatoryFee amount“3000”
> net_amountObjectMandatoryAmount received by beneficiary (gross - fee)-
>> currencyStringMandatoryCurrency code (ISO 4217)“IDR”
>> valueStringMandatoryNet amount value“47000”
> balance_afterObjectMandatoryAccount balance after transaction-
>> currencyStringConditionalCurrency code (null if failed)“IDR”
>> valueStringConditionalBalance value (null if failed)“777000”
> notesStringOptionalTransaction notes from request“bayar pajak”
> failed_codeNumericConditionalError code (only present when status is failed)SP003
> failed_reasonStringConditionalError message (only present when status is failed)INSUFFICIENT BALANCE

Response Example

Success: Here’s an example of a successful response.

{
    "response_code": "SP000",
    "response_message": "Successfully",
    "data": {
        "transaction_id": "101222025122910292195055674",
        "reference_number": "11111111118",
        "transaction_status": {
            "code": "00",
            "desc": "Success"
        },
        "post_timestamp": "1766978961000",
        "processed_timestamp": "1766978962000",
        "bank": {
            "code": "002",
            "name": "BRI",
            "account_name": "Dummy Test Account Internal",
            "account_number": "11111111118"
        },
        "gross_amount": {
            "currency": "IDR",
            "value": "12504.00"
        },
        "fee": {
            "currency": "IDR",
            "value": "2500"
        },
        "net_amount": {
            "currency": "IDR",
            "value": "10004.00"
        },
        "balance_after": {
            "currency": "IDR",
            "value": "829988"
        },
        "notes": "test transfer"
    }
}

Pending: example response.

{
    "response_code": "SP000",
    "response_message": "Successfully",
    "data": {
        "transaction_id": "101222025122910321260732119",
        "reference_number": "33311111111",
        "transaction_status": {
            "code": "03",
            "desc": "Pending"
        },
        "post_timestamp": "1766979132000",
        "processed_timestamp": "",
        "bank": {
            "code": "002",
            "name": "BRI",
            "account_name": "",
            "account_number": "33311111111"
        },
        "gross_amount": {
            "currency": "IDR",
            "value": "12506.00"
        },
        "fee": {
            "currency": "IDR",
            "value": "2500"
        },
        "net_amount": {
            "currency": "IDR",
            "value": "10006.00"
        },
        "balance_after": {
            "currency": "IDR",
            "value": "0"
        },
        "notes": "test transfer"
    }
}

Failed Transaction.

{
    "response_code": "SP001",
    "response_message": "Transaction Failure",
    "data": {
        "transaction_id": "121222025122617513896515436",
        "reference_number": "333",
        "transaction_status": {
            "code": "06",
            "desc": "Failed"
        },
        "post_timestamp": "1766746298000",
        "processed_timestamp": "",
        "bank": {
            "code": "002",
            "name": "BRI",
            "account_name": "",
            "account_number": "091701064838533"
        },
        "gross_amount": {
            "currency": "IDR",
            "value": "12501.00"
        },
        "fee": {
            "currency": "IDR",
            "value": "2500"
        },
        "net_amount": {
            "currency": "IDR",
            "value": "10001.00"
        },
        "balance_after": {
            "currency": "IDR",
            "value": "0"
        },
        "notes": "test transfer",
        "failed_reason": "Transaction Failure : Invalid beneficiary account: Account inactive",
        "failed_code": "SP001"
    }
}

Error: Duplicate reference_number

{
    "response_code": "SP004",
    "response_message": "Duplicate Reference Number",
    "data": {
        "account_id": "01K5G4FZZ18DMK0M5QTR8Y9QY9",
        "reference_number": "331",
        "amount": 61000,
        "notes": "test transfer",
        "bank_code": "BRINIDJA",
        "bank_account_number": "091701064838533",
        "message": "Transaction with this reference number already exists"
    }
}

Error: Validation

{
    "response_code": "SP018",
    "response_message": "Validation error",
    "data": {
        "account_id": "01K8J8GBTB88H9Z6DVMY1XMJP8",
        "reference_number": "331",
        "amount": 61000,
        "notes": "test transfer",
        "bank_code": null,
        "bank_account_number": "091701064838533",
        "message": "Validation error 'bank_code': 'The bank code field is required.'"
    }
}

Error: Insufficient Balance

{
    "response_code": "SP003",
    "response_message": "Insufficient Balance",
    "data": {
        "transaction_id": "291222025122617393593025327",
        "reference_number": "331",
        "transaction_status": {
            "code": "06",
            "desc": "Failed"
        },
        "post_timestamp": "1766745575000",
        "processed_timestamp": "",
        "bank": {
            "code": "002",
            "name": "BRI",
            "account_name": "",
            "account_number": "091701064838533"
        },
        "gross_amount": {
            "currency": "IDR",
            "value": "63500.00"
        },
        "fee": {
            "currency": "IDR",
            "value": "2500"
        },
        "net_amount": {
            "currency": "IDR",
            "value": "61000.00"
        },
        "balance_after": {
            "currency": "IDR",
            "value": "0"
        },
        "notes": "test transfer"
    }
}

Error: General Error

{
    "response_code": "SP019",
    "response_message": "General Error",
    "data": {
        "account_id": "01K5G4FZZ18DMK0M5QTR8Y9QY9",
        "reference_number": "331",
        "amount": 61000,
        "notes": "test transfer",
        "bank_code": "BRINIDJA",
        "bank_account_number": "091701064838533",
        "message": "internal server error"
    }
}

Error: Expired Access Token.

{
    "status": 401,
    "success": false,
    "error": {
        "code": 401,
        "message": "Unauthorized merchant, please sign in"
    }
}

Error: Invalid Signature.

{
    "status": 401,
    "success": false,
    "error": {
        "code": 4019900,
        "message": "Unauthorized. Invalid X-SIGNATURE"
    }
}

List of Bank Swift Codes Supported

Swift CodeNumber CodeFull NameShort Name
BRINIDJA2Bank Rakyat IndonesiaBRI
IBBKIDJA16Bank Maybank IndonesiaMaybank
BNINIDJA9Bank Negara IndonesiaBNI
BDINIDJA11Bank DanamonDanamon
CENAIDJA14Bank Central AsiaBCA
BMRIIDJA8Bank MandiriMandiri
BPIAIDJA47Bank ResonaBank Resona
CTCBIDJA949Bank Chinatrust IndonesiaBank Chinatrust
PINBIDJA19Bank PaninBank Panin
BUMIIDJA485Bank MNC InternasionalBank MNC
DBSBIDJA46Bank DBS IndonesiaBank DBS
PDKSIDJ1122BPD Kalimantan SelatanBank Kalsel
PDWRIDJ1135BPD Sulawesi TenggaraBank Sultra
SCBLIDJX50Standard Charted BankStandard Charted
ABALIDBS129BPD BaliBank Bali
PDJGIDJ1113BPD Jawa TengahBank Jateng
SYJBIDJ1425Bank BJB SyariahBJB Syariah
PDWGIDJ1134BPD Sulawesi TengahBank Sulteng
MEDHIDS1151Bank Mestika DharmaBank Mestika
BKCHIDJA69Bank of China HongkongBank Hongkong
PDJBIDJA110Bank Jawa Barat Dan BantenBank BJB
SBJKIDJA153Bank SinarmasBank Sinarmas
ANZBIDJX61Bank ANZ IndonesiaBank Anz
ARTGIDJA37Bank Artha Graha InternationalArtha Graha
BBUKIDJA441Bank BukopinBank Bukopin
BBAIIDJA76Bank Bumi ArtaBank Bumi Artha
BCIAIDJA54Bank Capital IndonesiaBank Capital
SYCAIDJ1536Bank Central Asia SyariahBCA Syariah
BICNIDJA950Bank CommonwealthBank Commonwealth
BDKIIDJ1111Bank DKIBank DKI
GNESIDJA161Bank GaneshaBank Ganesha
ICBKIDJA164Bank ICBC IndonesiaBank ICBC
IAPTIDJA513Bank Ina PerdaniaBank Ina
BIDXIDJA555Bank Index SelindoBank Index
MASDIDJ1157Bank Maspion IndonesiaBank Maspion
MAYAIDJA97Bank Mayapada InternationalBank Mayapada
MAYOIDJA553Bank MayoraBank Mayora
MEGAIDJA426Bank MegaBank Mega
BMSEIDJA548Bank Multi Arta SentosaMulti Arta
LFIBIDJ1503Bank NationalnobuBank Nobu
BOTKIDJX42Bank MUFGBank MUFG
BDIPIDJ1523Bank Sahabat SampoernaBank Sampoerna
SDOBIDJ1521Bank Syariah BukopinBukopin Syariah
BUTGIDJ1506Bank Syariah MegaMega Syariah
SUNIIDJA213Bank Tabungan Pensiunan Nasional (BTPN)Bank BTPN
BBIJIDJA23Bank UOB IndonesiaBank UOB
VICTIDJ1566Bank Victoria InternasionalBank Victoria
PDBKIDJ1133BPD BengkuluBank Bengkulu
PDYKIDJ1112BPD Daerah Istimewa YogyakartaBank DIY
PDJMIDJ1115BPD JambiBank Jambi
PDJTIDJ1114BPD Jawa TimurBank Jatim
PDKBIDJ1123BPD Kalimantan BaratBank Kalbar
PDKGIDJ1125BPD Kalimantan TengahBank Kalteng
PDLPIDJ1121BPD LampungBank Lampung
PDMLIDJ1131BPD Maluku Dan MalutBank Maluku Malut
PDNBIDJ1128BPD Nusa Tenggara BaratBank NTB
PDIJIDJ1132BPD PapuaBank Papua
PDWUIDJ1127BPD SulutBank Sulut
BSSPIDSP120BPD Sumsel Dan BabelBank Sulsel Babel
PDSUIDJ1117BPD SumutBank Sumut
CITIIDJX31CitibankCitibank
HSBCIDJA87HSBC IndonesiaHSBC
ALOBIDJA567Allo Bank IndonesiaAllo Bank
BSMDIDJA451Bank Syariah IndonesiaBSI
BNIAIDJA22CIMB NiagaCIMB
BBBAIDJA13Bank PermataPERMATA
MUABIDJA147Bank Muamalat IndonesiaMUAMALAT
NISPIDJA28OCBC IndonesiaOCBC
MEEKIDJ1152Bank Shinhan IndonesiaBank Shinhan
SIHBIDJ1564Mandiri Taspen PosBank Mandiri Taspen
AWANIDJA167Bank QNB IndonesiaBank QNB
PDBBIDJ1137BPD BantenBank Banten
CICTIDJA95Bank JTrust IndonesiaBank JTrust
PUBAIDJ1547BTPN SyariahBTPN Syariah
BSDRIDJA212Bank Woori Saudara Indonesia 1906Bank Woori Saudara
LMANIDJ1526Bank Oke IndonesiaBank Oke
NETBIDJA947Bank Aladin SyariahBank Aladin Syariah
IBKOIDJA945Bank IBK IndonesiaBank IBK
SSPIIDJA535Bank Seabank IndonesiaBank Seabank
BBLUIDJA501Bank BCA DIGITALBlu (BCA Digital)
JAGBIDJA542Bank JagoBank Jago
BUSTIDJ1459Krom Bank IndonesiaKrom Bank
HNBNIDJA484Bank Hana (Line)Bank Hana (Line)
PDRIIDJA119BPD RIAUBPD Riau
PDKTIDJ1124BPD KALIMANTAN TIMUR DAN KALIMANTAN UTARABPD Kaltim dan Kaltara
PDNTIDJA130BPD NUSA TENGGARA TIMURBPD NTT
PDWSIDJA126BANK SULSELBARBank Sulselbar
FAMAIDJ1562Superbank IndonesiaSuperbank
BTANIDJA200Bank Tabungan Negara (BTN)BTN
LOMAIDJ1531PT. BANK AMAR INDONESIABank Amar
SYACIDJ1116PT. BANK ACEH SYARIAHBank Aceh Syariah
AGTBIDJA494BANK RAYA INDONESIABank Raya
JSABIDJ1472Bank Jasa JakartaJasa Jakarta
SYBTIDJ1200Bank Tabungan Negara UUS (BTN Syariah)BTN Syariah
MCORIDJA36China Construction BankChina Construction Bank
SYNAIDJ122CIMB Niaga SyariahCIMB Niaga Syariah
NUPAIDJ6145Bank Nusantara ParahyanganBank Nusantara Parahyangan
BKIDIDJA146Bank of IndiaBank of India
ARFAIDJ1517Panin Dubai SyariahPanin Dubai Syariah
SYBBIDJ113Permata SyariahPermata Syariah
RABOIDJA89Rabobank IndonesiaRabobank Indonesia
INDOIDJA498Bank SBI IndonesiaBank SBI Indonesia
SWAGIDJ1405Victoria SyariahVictoria Syariah
SYSBIDJ1118Nagari SyariahNagari Syariah
688BPR Karyajatnika SadayaBPR Karyajatnika Sadaya
CNBAIDJ1559Centratama Nasional BankCentratama Nasional Bank
YUDBIDJ1490Bank Neo Commerce (BNC)BNC

For detailed information about response codes and transaction statuses, refer to:

How to Generate Request Signature

Every transaction request must include an X-Signature header that will be validated by SingaPay. If the signature is invalid, the request will be rejected.

Signature Algorithm

The signature uses HMAC-SHA512 algorithm with the following steps:

Step 1: Normalize the Request Body

Sort all JSON object keys recursively in alphabetical order, then encode using:

  • JSON_UNESCAPED_UNICODE - Don’t escape unicode characters
  • JSON_UNESCAPED_SLASHES - Don’t escape forward slashes

Example:

// Original body (key order doesn't matter)
{
  "notes": "bayar cicilan",
  "amount": 50000,
  "bank_swift_code": "BRINIDJA",
  "reference_number": "123456789123",
  "bank_account_number": "521398319083210"
}

// Normalized (keys sorted alphabetically)
{"amount":50000,"bank_account_number":"521398319083210","bank_swift_code":"BRINIDJA","notes":"bayar cicilan","reference_number":"123456789123"}

Step 2: Hash the Normalized Body

Hash the normalized JSON string using SHA-256:

hashedBody = SHA256(normalizedBody)

Example:

hashedBody = "a1b2c3d4e5f6789..."

Step 3: Build the String to Sign

Combine the following components with colon (:) separator:

stringToSign = METHOD:ENDPOINT:ACCESS_TOKEN:HASHED_BODY:TIMESTAMP

Where:

  • METHOD = HTTP method (e.g., POST)
  • ENDPOINT = Request URI path (e.g., /api/v1.0/disbursements/{account_id}/transfer)
  • ACCESS_TOKEN = Bearer token from Authorization header (without “Bearer ” prefix)
  • HASHED_BODY = SHA-256 hash from Step 2
  • TIMESTAMP = Unix timestamp in seconds (from X-Timestamp header)

Example:

POST:/api/v1.0/disbursements/abc123/transfer:eyJ0eXAiOiJKV1QiLCJhbGc...:a1b2c3d4e5f6789...:1714618220

Step 4: Generate HMAC-SHA512 Signature

Generate the signature using HMAC-SHA512 with your client_secret as the key:

signature = HMAC_SHA512(stringToSign, client_secret)

The output is a hexadecimal string (not base64).

Code Examples

PHP Example

<?php
function generateSignature($method, $endpoint, $accessToken, $body, $timestamp, $clientSecret) {
    // Step 1: Normalize body
    $bodyArray = json_decode($body, true);
    ksort($bodyArray);
    array_walk_recursive($bodyArray, function(&$item) {
        if (is_array($item)) ksort($item);
    });
    $normalizedBody = json_encode($bodyArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    // Step 2: Hash normalized body
    $hashedBody = hash('sha256', $normalizedBody);

    // Step 3: Build string to sign
    $stringToSign = "{$method}:{$endpoint}:{$accessToken}:{$hashedBody}:{$timestamp}";

    // Step 4: Generate HMAC-SHA512 signature
    $signature = hash_hmac('sha512', $stringToSign, $clientSecret);

    return $signature;
}

// Usage
$signature = generateSignature(
    'POST',
    '/api/v1.0/disbursements/abc123/transfer',
    'eyJ0eXAiOiJKV1QiLCJhbGc...',
    '{"amount":50000,"bank_account_number":"521398319083210","bank_swift_code":"BRINIDJA","notes":"bayar cicilan","reference_number":"123456789123"}',
    '1714618220',
    'your_client_secret_here'
);
?>

JavaScript/Node.js Example

const crypto = require('crypto');

function sortObjectKeys(obj) {
    if (typeof obj !== 'object' || obj === null) return obj;
    if (Array.isArray(obj)) return obj.map(sortObjectKeys);

    return Object.keys(obj)
        .sort()
        .reduce((sorted, key) => {
            sorted[key] = sortObjectKeys(obj[key]);
            return sorted;
        }, {});
}

function generateSignature(method, endpoint, accessToken, body, timestamp, clientSecret) {
    // Step 1: Normalize body
    const bodyObject = JSON.parse(body);
    const sortedBody = sortObjectKeys(bodyObject);
    const normalizedBody = JSON.stringify(sortedBody);

    // Step 2: Hash normalized body
    const hashedBody = crypto
        .createHash('sha256')
        .update(normalizedBody)
        .digest('hex');

    // Step 3: Build string to sign
    const stringToSign = `${method}:${endpoint}:${accessToken}:${hashedBody}:${timestamp}`;

    // Step 4: Generate HMAC-SHA512 signature
    const signature = crypto
        .createHmac('sha512', clientSecret)
        .update(stringToSign)
        .digest('hex');

    return signature;
}

// Usage
const signature = generateSignature(
    'POST',
    '/api/v1.0/disbursements/abc123/transfer',
    'eyJ0eXAiOiJKV1QiLCJhbGc...',
    '{"amount":50000,"bank_account_number":"521398319083210","bank_swift_code":"BRINIDJA","notes":"bayar cicilan","reference_number":"123456789123"}',
    '1714618220',
    'your_client_secret_here'
);

Python Example

import hashlib
import hmac
import json

def sort_dict_recursive(obj):
    if isinstance(obj, dict):
        return {k: sort_dict_recursive(v) for k, v in sorted(obj.items())}
    elif isinstance(obj, list):
        return [sort_dict_recursive(item) for item in obj]
    return obj

def generate_signature(method, endpoint, access_token, body, timestamp, client_secret):
    # Step 1: Normalize body
    body_dict = json.loads(body)
    sorted_body = sort_dict_recursive(body_dict)
    normalized_body = json.dumps(sorted_body, ensure_ascii=False, separators=(',', ':'))

    # Step 2: Hash normalized body
    hashed_body = hashlib.sha256(normalized_body.encode()).hexdigest()

    # Step 3: Build string to sign
    string_to_sign = f"{method}:{endpoint}:{access_token}:{hashed_body}:{timestamp}"

    # Step 4: Generate HMAC-SHA512 signature
    signature = hmac.new(
        client_secret.encode(),
        string_to_sign.encode(),
        hashlib.sha512
    ).hexdigest()

    return signature

# Usage
signature = generate_signature(
    'POST',
    '/api/v1.0/disbursements/abc123/transfer',
    'eyJ0eXAiOiJKV1QiLCJhbGc...',
    '{"amount":50000,"bank_account_number":"521398319083210","bank_swift_code":"BRINIDJA","notes":"bayar cicilan","reference_number":"123456789123"}',
    '1714618220',
    'your_client_secret_here'
)

Important Notes

  1. Timestamp Format: Must be Unix timestamp in seconds (not milliseconds, not ISO 8601)
  2. Access Token: Extract from Authorization header without “Bearer ” prefix
  3. Endpoint: Must include the full path starting with /api/v1.0/... and include the {account_id} parameter
  4. JSON Normalization: Keys must be sorted recursively for nested objects
  5. Hash Output: Use hexadecimal encoding (not base64)
  6. Client Secret: Use your client_secret from merchant credentials, NOT your client_id or api_key

Security Validation

SingaPay will:

  1. Verify the X-PARTNER-ID matches an existing API key
  2. Retrieve the corresponding client_secret from the credentials table
  3. Regenerate the signature using the same algorithm
  4. Use constant-time comparison (hash_equals) to prevent timing attacks
  5. Reject the request if signatures don’t match