Signature
Algorithm
Summary: sort parameters by ASCII (ascending), exclude empty values, build a querystring, then append &key=YOUR_SECRET_KEY at the end. (Parameter names are lowercase by default.) sign itself is NOT included in the signature.
General steps:
- Put all request/response parameters into a set M. Sort the non-empty parameters in M by parameter name ASCII (lexicographical order). Join them into
stringAusing URL key-value format:key1=value1&key2=value2.... - Append
&key=+ your secret key tostringAto formstringSignTemp, then calculate MD5 ofstringSignTempto getsign.
Important Rules
- Sort parameter names by ASCII (lexicographical order).
- Parameters with empty values are not included in the signature.
- All parameter names are lowercase.
- When verifying a response or an async callback, the incoming
signis not included in the signature. Generate a signature from the other fields and compare it with the receivedsign. - The API may add new fields. Signature verification must support additional fields.
- URLs and other special characters must be URL-encoded.
- Spaces must be escaped as
+(different languages may behave differently; pay attention). - Do not convert the final signature string to uppercase.
- When the payout account info is a JSON object (
acct), flatten it asacct.key1,acct.key2... (see acct Flattening).
Example (Step by Step)
Example parameters:
{
"merid": "12345",
"amt": "1.00",
"tno": "123123123123",
"noti_url": "https://www.baidu.com",
"extra": ""
}
- Build the raw signature string
- Remove empty values
- URL-encode
- Sort by parameter name ASCII order
- Join as
key1=value1&key2=value2&key3=value3...
Result:
amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123
- Append
&key=YOUR_SECRET_KEY
amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123&key=xxxxxxxxx
- Calculate MD5
signresult=md5("amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123&key=xxxxxxxxx")
Final request parameters:
{
"merid": "12345",
"amt": "100.00",
"tno": "123123123123",
"noti_url": "https://www.baidu.com",
"extra": "",
"sign": "signresult"
}
acct Flattening
For acct in payout/collection requests, flatten it as follows. Given:
{
"acct": {
"code": "PH_GCASH",
"name": "GCash",
"number": "09451187823",
"type": "bankcard",
"user": "EMHER AAA"
},
"amt": "2100.00",
"cur": "PHP",
"merid": "99999",
"notify_url": "http://cb.abcd.com/callback.do",
"tno": "72245523588"
}
The final joined string is:
acct.code=PH_GCASH&acct.name=GCash&acct.number=09451187823&acct.type=bankcard&acct.user=EMHER+AAA&amt=2100.00&cur=PHP&merid=96182¬ify_url=http%3A%2F%2Fcb.abcd.com%2Fcallback.do&tno=72245523588
MD5 calculation is the same:
md5(str+"&key="key)
!! Note:
During signing, both parameter names and values must be URL-encoded following the `application/x-www-form-urlencoded` encoding rules, NOT `RFC 3986`. The code below was converted by GPT, so if your urlencode function behaves differently, pay attention to this requirement.
During signing, both parameter names and values must be URL-encoded following the `application/x-www-form-urlencoded` encoding rules, NOT `RFC 3986`. The code below was converted by GPT, so if your urlencode function behaves differently, pay attention to this requirement.
Reference Code
PHP Example
<?php
function data_encode(array $data)
{
$flat = [];
flatten_data('', $data, $flat);
if (empty($flat)) return '';
ksort($flat); // asc
$out = [];
foreach ($flat as $k => $v) {
if ($v === '') continue; // strip = 1
$out[] = urlencode($k) . '=' . urlencode($v); // urlencode = 1
}
return implode('&', $out);
}
function flatten_data(string $prefix, array $data, array &$out)
{
foreach ($data as $k => $v) {
$key = $prefix ? $prefix . '.' . $k : $k;
if (is_array($v)) {
foreach ($v as $kk => $vv) {
$out[$key . '.' . $kk] = (string)$vv;
}
} else {
$out[$key] = (string)$v;
}
}
}
$data = [
"merid" => "12345",
"amt" => "100.01",
"noti_url" => "https://www.baidu.com",
"acct" => [
"type" => "bank",
"card" => "22222222",
],
];
$your_api_key="keyekeykeykeyabcd";
echo "data-string".data_encode($data)."<br/>";
echo "sign-string".data_encode($data)."&key=".$your_api_key;
C# Example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
class Program
{
static string DataEncode(Dictionary<string, object> data)
{
var flat = new Dictionary<string, string>();
FlattenData("", data, flat);
if (flat.Count == 0) return "";
var keys = flat.Keys.OrderBy(k => k).ToList();
var outList = new List<string>();
foreach (var k in keys)
{
var v = flat[k];
if (v == "") continue;
outList.Add(WebUtility.UrlEncode(k) + "=" + WebUtility.UrlEncode(v));
}
return string.Join("&", outList);
}
static void FlattenData(string prefix, Dictionary<string, object> data, Dictionary<string, string> flat)
{
foreach (var kv in data)
{
string key = prefix == "" ? kv.Key : prefix + "." + kv.Key;
if (kv.Value is Dictionary<string, object> map)
{
foreach (var m in map)
{
flat[key + "." + m.Key] = m.Value.ToString();
}
}
else
{
flat[key] = kv.Value.ToString();
}
}
}
static void Main()
{
var data = new Dictionary<string, object>
{
{"merid", "12345"},
{"amt", "100.01"},
{"noti_url", "https://www.baidu.com"},
{ "acct", new Dictionary<string, object>{
{"type", "bank"},
{"card", "22222222"},
}},
};
string apiKey = "keyekeykeykeyabcd";
Console.WriteLine("data-string " + DataEncode(data));
Console.WriteLine("sign-string " + DataEncode(data) + "&key=" + apiKey);
}
}
Java Example
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class Main {
static String dataEncode(Map<String, Object> data) {
Map<String, String> flat = new HashMap<>();
flattenData("", data, flat);
if (flat.isEmpty()) return "";
List<String> keys = new ArrayList<>(flat.keySet());
Collections.sort(keys);
List<String> out = new ArrayList<>();
for (String k : keys) {
String v = flat.get(k);
if (v.equals("")) continue;
out.add(url(k) + "=" + url(v));
}
return String.join("&", out);
}
static void flattenData(String prefix, Map<String, Object> data, Map<String, String> out) {
for (Map.Entry<String, Object> e : data.entrySet()) {
String key = prefix.isEmpty() ? e.getKey() : prefix + "." + e.getKey();
Object v = e.getValue();
if (v instanceof Map) {
Map<?, ?> m = (Map<?, ?>) v;
for (Map.Entry<?, ?> sub : m.entrySet()) {
out.put(key + "." + sub.getKey(), sub.getValue().toString());
}
} else {
out.put(key, v.toString());
}
}
}
static String url(String s) {
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
Map<String, Object> data = new HashMap<>();
data.put("merid", "12345");
data.put("amt", "100.01");
data.put("noti_url", "https://www.baidu.com");
Map<String, Object> acct = new HashMap<>();
acct.put("type", "bank");
acct.put("card", "22222222");
data.put("acct", acct);
String apiKey = "keyekeykeykeyabcd";
System.out.println("data-string " + dataEncode(data));
System.out.println("sign-string " + dataEncode(data) + "&key=" + apiKey);
}
}
Go Example
package main
import (
"fmt"
"net/url"
"sort"
"strings"
)
func dataEncode(data map[string]any) string {
flat := map[string]string{}
flattenData("", data, flat)
if len(flat) == 0 {
return ""
}
keys := make([]string, 0, len(flat))
for k := range flat {
keys = append(keys, k)
}
sort.Strings(keys) // asc
var out []string
for _, k := range keys {
v := flat[k]
if v == "" { // strip = 1
continue
}
out = append(out, url.QueryEscape(k)+"="+url.QueryEscape(v)) // urlencode = 1
}
return strings.Join(out, "&")
}
func flattenData(prefix string, data map[string]any, out map[string]string) {
for k, v := range data {
key := k
if prefix != "" {
key = prefix + "." + k
}
switch vv := v.(type) {
case map[string]any:
for kk, vvv := range vv {
out[key+"."+kk] = fmt.Sprintf("%v", vvv)
}
default:
out[key] = fmt.Sprintf("%v", v)
}
}
}
func main() {
data := map[string]any{
"merid": "12345",
"amt": "100.01",
"noti_url": "https://www.baidu.com",
"acct": map[string]any{
"type": "bank",
"card": "22222222",
},
}
yourAPIKey := "keyekeykeykeyabcd"
fmt.Println("data-string", dataEncode(data))
fmt.Println("sign-string", dataEncode(data)+"&key="+yourAPIKey)
}
Python Example
import urllib.parse
def data_encode(data):
flat = {}
flatten_data("", data, flat)
if not flat:
return ""
out = []
for k in sorted(flat.keys()):
v = flat[k]
if v == "":
continue
# Force Python encoding behavior to match Go's QueryEscape
key_enc = urllib.parse.quote(k, safe="")
val_enc = urllib.parse.quote(v, safe="").replace("%20", "+") # Space -> +
out.append(key_enc + "=" + val_enc)
return "&".join(out)
def flatten_data(prefix, data, out):
for k, v in data.items():
key = k if prefix == "" else prefix + "." + k
if isinstance(v, dict):
for kk, vv in v.items():
out[key + "." + kk] = str(vv)
else:
out[key] = str(v)
data = {
"merid": "12345",
"amt": "100.01",
"ctm": "2025-11-21 01:37:27",
"noti_url": "https://api.kj1616.com/api/pay/callback/2o6i9i7rrsHJ9ghFn64u8o",
"acct": {
"type": "bank",
"card": "22222222",
},
}
api_key = "keyekeykeykeyabcd"
print("data-string", data_encode(data))
print("sign-string", data_encode(data) + "&key=" + api_key)