Signature Guide
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:
| Algorithm | sign_type Value | Recommendation | Description |
|---|---|---|---|
| HMAC-SHA256 | HMAC-SHA256 | ⭐ Recommended | More secure signing algorithm |
| MD5 | MD5 or omit | Legacy | Kept for backward compatibility |
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:
| Field | Value |
|---|---|
| Merchant ID (platform_id) | PF0002 |
| Platform Key (platform_key) | ThisIsYourSecretKey123 |
HMAC-SHA256 Signature (Recommended)
Signing Rules
- Sort parameters: Sort all parameters by key in ASCII ascending order
- Exclude parameters:
sign,sign_type, and parameters with empty values - Concatenate string: Join as
key=valuepairs connected by& - HMAC-SHA256 sign: Use the key to sign the string with HMAC-SHA256
- Convert to lowercase: Convert the result to a 64-character lowercase hexadecimal string
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"]
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¬ify_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¬ify_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
MD5 is only kept for backward compatibility. New merchants should use HMAC-SHA256.
Signing Rules
- Sort parameters: Sort all parameters by key in ASCII ascending order
- Exclude parameters:
sign,sign_type, and parameters with empty values - Concatenate string: Join as
key=valuepairs connected by& - Append key: Append
&{platform_key}at the end (without akey=prefix) - 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¬ify_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¬ify_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:
- Extract
sign_typefrom the callback parameters (if present) - Use the same algorithm to recalculate the signature
- Compare the calculated result with the
signvalue
Always verify callback signatures to prevent forged callback attacks.
Common Issues
Signature Error (error_code: 0004)
Common causes:
- Wrong MD5 key appending format - Should be
&{platform_key}, NOT&key={platform_key} - Inconsistent parameter values - Ensure the parameter values used for signing match the actual request exactly
- Not excluding sign_type - Both
signandsign_typeshould be excluded from signature calculation - Encoding issues - Ensure UTF-8 encoding is used