Singapay Home Page
Logo Icon
  1. E-Wallet (Money Out)
  2. Trigger Top Up

Information

MethodPathFormatAuthentication
POST/api/v2.0/ewallet/trigger-topupjsonOAuth 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

KeyData TypeMandatoryDescription
reference_numberStringYESUnique transaction identifier (Max: 64 chars)
account_idStringYESYour Account ID
ewallet_codeStringYESE-Wallet vendor code (e.g., DANA, SHOPEEPAY, OVO, GOPAY, SINGAPAY)
customer_numberStringYESCustomer’s e-wallet account number (phone number) to top up
amountObjectYESNet amount to be received by customer
> valueString (ISO 4217)YESAmount with 2 decimal digits (e.g., IDR 10,000 = “10000.00”)
> currencyString (ISO 4217)YESCurrency code (IDR)

Response

Response Body

KeyData TypeMandatoryDescription
response_codeStringYESResponse code (see Response Code appendix)
response_messageStringYESResponse message (see Response Code appendix)
dataObjectNoConditional parameter (present on success)
> transaction_idStringYESID transaction created by system
> transaction_statusObjectYESTransaction status (see Transaction Status appendix)
>> codeStringYESStatus code (e.g., “00”, “03”)
>> descStringYESStatus description (e.g., “Success”, “Pending”)
> reference_numberStringYESYour unique transaction identifier (Max: 64 chars)
> ewalletObjectYESE-wallet details
>> codeStringYESE-Wallet vendor code
>> customer_numberStringYESCustomer’s e-wallet account number
>> customer_nameStringYESCustomer’s e-wallet account name
> post_timestampStringYESRequest time with Unix timestamp millisecond format
> processed_timestampStringYESFinish processed time with Unix timestamp millisecond format
> balance_afterObjectYESBalance after transaction of account
>> valueString (ISO 4217)YESAmount with 2 decimal digits
>> currencyString (ISO 4217)YESCurrency code (IDR)
> net_amountObjectYESNet amount received by customer
>> valueString (ISO 4217)YESAmount with 2 decimal digits
>> currencyString (ISO 4217)YESCurrency code (IDR)
> feeObjectYESTotal fee per transaction charged to merchant (based on agreement)
>> valueString (ISO 4217)YESAmount with 2 decimal digits
>> currencyString (ISO 4217)YESCurrency code (IDR)
> gross_amountObjectYESTotal balance deducted from merchant account (net_amount + fee)
>> valueString (ISO 4217)YESAmount with 2 decimal digits
>> currencyString (ISO 4217)YESCurrency code (IDR)

Examples

Request Example

{
  "account_id": "01K5G4FZZ18DMK0M5QTR8Y9QY9",
  "reference_number": "735463554",
  "ewallet_code": "DANA",
  "customer_number": "085733347341",
  "amount": {
    "value": "11000.00",
    "currency": "IDR"
  }
}

Response Example - Success

{
  "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 Example - Error

{
  "response_code": "SP117",
  "response_message": "Beneficiary Account Not Found",
  "data": {
    "ewallet_code": "DANA",
    "reference_number": "176458254538481",
    "customer_number": "082199722404"
  }
}

Important Notes

  • Always perform Inquiry Account before triggering a top-up
  • For sandbox testing, you can use the following customer number: 62811742234
  • The reference_number must be unique for each transaction
  • Transaction may initially return with status “Pending” (code: “03”) - use Inquiry Status to check final status
  • Check the transaction_status in the response to determine next steps
  • The gross_amount is what will be deducted from your merchant account
  • The net_amount is what the customer will receive in their e-wallet
  • Refer to the Response Code appendix for all possible response codes
  • Refer to the Transaction Status appendix for all possible transaction statuses

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)
{
  "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"}

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/v2.0/ewallet/trigger-topup)
  • 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/v2.0/ewallet/trigger-topup: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/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'
);
?>

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/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'
);

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/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'
)

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 /api/v2.0/ewallet/trigger-topup
  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