Skip to main content

Signature Guide

MD5 Signature Algorithm Deprecation

MD5 signature algorithm will be deprecated before 2026/03/31. Please migrate to HMAC-SHA256 before this date. Requests using MD5 will return errors after deprecation.

To ensure secure data transmission, we verify request parameters using digital signatures.

Signature Algorithms

We support the following two signature algorithms:

Algorithmsign_type ValueRecommendationDescription
HMAC-SHA256HMAC-SHA256⭐ RecommendedMore secure signing algorithm
MD5MD5 or omitLegacyKept for backward compatibility
For New Merchants

We strongly recommend using HMAC-SHA256. MD5 is considered insecure by modern cryptographic standards.

How to Choose

Add the sign_type parameter to your request to specify the signing algorithm:

{
"platform_id": "PF0002",
"sign_type": "HMAC-SHA256",
"sign": "..."
}

If sign_type is omitted, MD5 is used by default for backward compatibility.


Test Merchant Information

All examples below use the same test merchant information:

FieldValue
Merchant ID (platform_id)PF0002
Platform Key (platform_key)ThisIsYourSecretKey123

Signing Rules

  1. Sort parameters: Sort all parameters by key in ASCII ascending order
  2. Exclude parameters: sign, sign_type, and parameters with empty values
  3. Concatenate string: Join as key=value pairs connected by &
  4. HMAC-SHA256 sign: Use the key to sign the string with HMAC-SHA256
  5. Convert to lowercase: Convert the result to a 64-character lowercase hexadecimal string
Difference from MD5

HMAC-SHA256 uses the key directly for signing. Do NOT append the key to the string.

Array Parameter Handling

When request parameters contain arrays (e.g., last_numbers), the array must be converted to a JSON string format for signing.

Example: When last_numbers is ["12345", "67890"]:

last_numbers=["12345","67890"]
Note

When converting arrays to JSON strings, there must be no extra spaces. Elements should only be separated by commas.

Deposit Example

Request Parameters:

{
"platform_id": "PF0002",
"service_id": "SVC0001",
"payment_cl_id": "DEVPM00014581",
"amount": "50000",
"notify_url": "https://your-domain.com/callback",
"request_time": "1595504136",
"sign_type": "HMAC-SHA256"
}

Step 1: Sort and concatenate (excluding sign, sign_type)

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001

Step 2: HMAC-SHA256 Sign

Sign using key ThisIsYourSecretKey123, resulting in:

e8a5c3f2d1b4a6e9c7f0d2b5a8e1c4f7d0b3a6e9c2f5d8b1a4e7c0f3d6b9a2e5

Code Examples

cURL
# 1. Concatenate sorted parameters (excluding sign and sign_type)
PARAM_STR="amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001"
PLATFORM_KEY="ThisIsYourSecretKey123"

# 2. HMAC-SHA256 sign
SIGN=$(echo -n "$PARAM_STR" | openssl dgst -sha256 -hmac "$PLATFORM_KEY" | awk '{print $2}')
echo $SIGN
Python
import hmac
import hashlib

def generate_sign_hmac_sha256(params: dict, platform_key: str) -> str:
filtered = {k: v for k, v in params.items()
if v and k not in ['sign', 'sign_type']}
sorted_keys = sorted(filtered.keys())
param_str = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])

return hmac.new(
platform_key.encode('utf-8'),
param_str.encode('utf-8'),
hashlib.sha256
).hexdigest().lower()
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class SignatureUtil {
public static String generateHmacSha256(Map<String, String> params, String key) {
TreeMap<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, String> e : params.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()
&& !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) {
filtered.put(e.getKey(), e.getValue());
}
}

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : filtered.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}

try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
PHP
<?php
function generateSignHmacSha256(array $params, string $platformKey): string {
$filtered = array_filter($params, function($v, $k) {
return !empty($v) && !in_array($k, ['sign', 'sign_type']);
}, ARRAY_FILTER_USE_BOTH);

ksort($filtered);
$paramStr = http_build_query($filtered, '', '&');

return strtolower(hash_hmac('sha256', $paramStr, $platformKey));
}
?>
Go
package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"sort"
"strings"
)

func GenerateSignHmacSha256(params map[string]string, platformKey string) string {
var keys []string
for k, v := range params {
if v != "" && k != "sign" && k != "sign_type" {
keys = append(keys, k)
}
}
sort.Strings(keys)

var pairs []string
for _, k := range keys {
pairs = append(pairs, k+"="+params[k])
}
paramStr := strings.Join(pairs, "&")

h := hmac.New(sha256.New, []byte(platformKey))
h.Write([]byte(paramStr))
return strings.ToLower(hex.EncodeToString(h.Sum(nil)))
}
JavaScript
const crypto = require('crypto');

function generateSignHmacSha256(params, platformKey) {
// Filter and sort
const filtered = Object.entries(params)
.filter(([k, v]) => v && k !== 'sign' && k !== 'sign_type')
.sort(([a], [b]) => a.localeCompare(b));

// Concatenate
const paramStr = filtered.map(([k, v]) => `${k}=${v}`).join('&');

// HMAC-SHA256
return crypto
.createHmac('sha256', platformKey)
.update(paramStr)
.digest('hex')
.toLowerCase();
}

MD5 Signature (Legacy) - Click to expand
Not Recommended

MD5 is only kept for backward compatibility. New merchants should use HMAC-SHA256.

Signing Rules

  1. Sort parameters: Sort all parameters by key in ASCII ascending order
  2. Exclude parameters: sign, sign_type, and parameters with empty values
  3. Concatenate string: Join as key=value pairs connected by &
  4. Append key: Append &{platform_key} at the end (without a key= prefix)
  5. MD5 hash: Convert to a 32-character lowercase string

Deposit Example

Request Parameters (MD5 is used by default when sign_type is omitted):

{
"platform_id": "PF0002",
"service_id": "SVC0001",
"payment_cl_id": "DEVPM00014581",
"amount": "50000",
"notify_url": "https://your-domain.com/callback",
"request_time": "1595504136"
}

Step 1: Sort and concatenate

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001

Step 2: Append key

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001&ThisIsYourSecretKey123

Step 3: MD5 hash

49be5fa304b5f536c6e2ea89435e211a

Code Examples

Python
import hashlib

def generate_sign_md5(params: dict, platform_key: str) -> str:
filtered = {k: v for k, v in params.items()
if v and k not in ['sign', 'sign_type']}
sorted_keys = sorted(filtered.keys())
param_str = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])
sign_str = f'{param_str}&{platform_key}'
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().lower()
Java
import java.security.MessageDigest;
import java.util.*;

public class SignatureUtil {
public static String generateMd5(Map<String, String> params, String key) {
TreeMap<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, String> e : params.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()
&& !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) {
filtered.put(e.getKey(), e.getValue());
}
}

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : filtered.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
sb.append("&").append(key);

try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02x", b));
return hex.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

Verify Callback Signature

When receiving callback notifications, please verify the signature to ensure data has not been tampered with:

  1. Extract sign_type from the callback parameters (if present)
  2. Use the same algorithm to recalculate the signature
  3. Compare the calculated result with the sign value
Security Advice

Always verify callback signatures to prevent forged callback attacks.


Common Issues

Signature Error (error_code: 0004)

Common causes:

  1. Wrong MD5 key appending format - Should be &{platform_key}, NOT &key={platform_key}
  2. Inconsistent parameter values - Ensure the parameter values used for signing match the actual request exactly
  3. Not excluding sign_type - Both sign and sign_type should be excluded from signature calculation
  4. Encoding issues - Ensure UTF-8 encoding is used