
E-Wallet (Money Out)
Initiate and process an e-wallet top-up transaction after account inquiry.
| Method | Path | Format | Authentication |
|---|---|---|---|
| POST | /api/v2.0/ewallet/trigger-topup | json | OAuth 2.0 with Access Token |
| Field | Value | Type | Mandatory | Length | Description | Example |
|---|---|---|---|---|---|---|
| X-PARTNER-ID | api_key | Alphanumeric | Mandatory | API key obtained from the merchant dashboard. | b3ed7d4b-a96c-6c08-b3c7-12c3124242d9 | |
| Accept | application/json | Alphabetic | Mandatory | Specifies JSON as the expected response format. | application/json | |
| Authorization | Bearer {bearerToken} | Alphanumeric | Mandatory | Bearer token obtained from the get access token endpoint. | Bearer eyJ0eXAiOiJKV1… | |
| X-Signature | HMAC-SHA512 Signature | Alphanumeric | Mandatory | HMAC-SHA512 signature for request authentication. See signature generation guide below. | a1b2c3d4e5f6… | |
| X-Timestamp | Unix Timestamp (seconds) | Numeric | Mandatory | Request timestamp in Unix seconds format | 1714618220 |
| Key | Data Type | Mandatory | Description |
|---|---|---|---|
| reference_number | String | YES | Unique transaction identifier (Max: 64 chars) |
| account_id | String | YES | Your Account ID |
| ewallet_code | String | YES | E-Wallet vendor code (e.g., DANA, SHOPEEPAY, OVO, GOPAY, SINGAPAY) |
| customer_number | String | YES | Customer’s e-wallet account number (phone number) to top up |
| amount | Object | YES | Net amount to be received by customer |
| > value | String (ISO 4217) | YES | Amount with 2 decimal digits (e.g., IDR 10,000 = “10000.00”) |
| > currency | String (ISO 4217) | YES | Currency code (IDR) |
| Key | Data Type | Mandatory | Description |
|---|---|---|---|
| response_code | String | YES | Response code (see Response Code appendix) |
| response_message | String | YES | Response message (see Response Code appendix) |
| data | Object | No | Conditional parameter (present on success) |
| > transaction_id | String | YES | ID transaction created by system |
| > transaction_status | Object | YES | Transaction status (see Transaction Status appendix) |
| >> code | String | YES | Status code (e.g., “00”, “03”) |
| >> desc | String | YES | Status description (e.g., “Success”, “Pending”) |
| > reference_number | String | YES | Your unique transaction identifier (Max: 64 chars) |
| > ewallet | Object | YES | E-wallet details |
| >> code | String | YES | E-Wallet vendor code |
| >> customer_number | String | YES | Customer’s e-wallet account number |
| >> customer_name | String | YES | Customer’s e-wallet account name |
| > post_timestamp | String | YES | Request time with Unix timestamp millisecond format |
| > processed_timestamp | String | YES | Finish processed time with Unix timestamp millisecond format |
| > balance_after | Object | YES | Balance after transaction of account |
| >> value | String (ISO 4217) | YES | Amount with 2 decimal digits |
| >> currency | String (ISO 4217) | YES | Currency code (IDR) |
| > net_amount | Object | YES | Net amount received by customer |
| >> value | String (ISO 4217) | YES | Amount with 2 decimal digits |
| >> currency | String (ISO 4217) | YES | Currency code (IDR) |
| > fee | Object | YES | Total fee per transaction charged to merchant (based on agreement) |
| >> value | String (ISO 4217) | YES | Amount with 2 decimal digits |
| >> currency | String (ISO 4217) | YES | Currency code (IDR) |
| > gross_amount | Object | YES | Total balance deducted from merchant account (net_amount + fee) |
| >> value | String (ISO 4217) | YES | Amount with 2 decimal digits |
| >> currency | String (ISO 4217) | YES | Currency code (IDR) |
{
"account_id": "01K5G4FZZ18DMK0M5QTR8Y9QY9",
"reference_number": "735463554",
"ewallet_code": "DANA",
"customer_number": "085733347341",
"amount": {
"value": "11000.00",
"currency": "IDR"
}
}
{
"response_code": "SP000",
"response_message": "Successful",
"data": {
"transaction_id": "112220251111135424691",
"transaction_status": {
"code": "03",
"desc": "Pending"
},
"reference_number": "123456789123",
"ewallet": {
"code": "DANA",
"customer_number": "082199722404",
"customer_name": "Adnan"
},
"post_timestamp": "1762844064000",
"processed_timestamp": "1762844065000",
"balance_after": {
"value": "120000.00",
"currency": "IDR"
},
"net_amount": {
"value": "21000.00",
"currency": "IDR"
},
"fee": {
"value": "500.00",
"currency": "IDR"
},
"gross_amount": {
"value": "21500.00",
"currency": "IDR"
}
}
}
{
"response_code": "SP117",
"response_message": "Beneficiary Account Not Found",
"data": {
"ewallet_code": "DANA",
"reference_number": "176458254538481",
"customer_number": "082199722404"
}
}
62811742234reference_number must be unique for each transactiontransaction_status in the response to determine next stepsgross_amount is what will be deducted from your merchant accountnet_amount is what the customer will receive in their e-walletEvery transaction request must include an X-Signature header that will be validated by SingaPay. If the signature is invalid, the request will be rejected.
The signature uses HMAC-SHA512 algorithm with the following steps:
Sort all JSON object keys recursively in alphabetical order, then encode using:
JSON_UNESCAPED_UNICODE - Don’t escape unicode charactersJSON_UNESCAPED_SLASHES - Don’t escape forward slashesExample:
// Original body (key order doesn't matter)
{
"account_id": "01K5G4FZZ18DMK0M5QTR8Y9QY9",
"reference_number": "735463554",
"ewallet_code": "DANA",
"customer_number": "085733347341",
"amount": {
"value": "11000.00",
"currency": "IDR"
}
}
// Normalized (keys sorted alphabetically)
{"account_id":"01K5G4FZZ18DMK0M5QTR8Y9QY9","amount":{"currency":"IDR","value":"11000.00"},"customer_number":"085733347341","ewallet_code":"DANA","reference_number":"735463554"}
Hash the normalized JSON string using SHA-256:
hashedBody = SHA256(normalizedBody)
Example:
hashedBody = "a1b2c3d4e5f6789..."
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/v2.0/ewallet/trigger-topup)ACCESS_TOKEN = Bearer token from Authorization header (without “Bearer ” prefix)HASHED_BODY = SHA-256 hash from Step 2TIMESTAMP = Unix timestamp in seconds (from X-Timestamp header)Example:
POST:/api/v2.0/ewallet/trigger-topup:eyJ0eXAiOiJKV1QiLCJhbGc...:a1b2c3d4e5f6789...:1714618220
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).
<?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/v2.0/ewallet/trigger-topup',
'eyJ0eXAiOiJKV1QiLCJhbGc...',
'{"account_id":"01K5G4FZZ18DMK0M5QTR8Y9QY9","reference_number":"735463554","ewallet_code":"DANA","customer_number":"085733347341","amount":{"value":"11000.00","currency":"IDR"}}',
'1714618220',
'your_client_secret_here'
);
?>
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/v2.0/ewallet/trigger-topup',
'eyJ0eXAiOiJKV1QiLCJhbGc...',
'{"account_id":"01K5G4FZZ18DMK0M5QTR8Y9QY9","reference_number":"735463554","ewallet_code":"DANA","customer_number":"085733347341","amount":{"value":"11000.00","currency":"IDR"}}',
'1714618220',
'your_client_secret_here'
);
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/v2.0/ewallet/trigger-topup',
'eyJ0eXAiOiJKV1QiLCJhbGc...',
'{"account_id":"01K5G4FZZ18DMK0M5QTR8Y9QY9","reference_number":"735463554","ewallet_code":"DANA","customer_number":"085733347341","amount":{"value":"11000.00","currency":"IDR"}}',
'1714618220',
'your_client_secret_here'
)
/api/v2.0/ewallet/trigger-topupclient_secret from merchant credentials, NOT your client_id or api_keySingaPay will:
X-PARTNER-ID matches an existing API keyclient_secret from the credentials tablehash_equals) to prevent timing attacks