Signatures
Requests, responses and callbacks all carry a signature sign. The two APIs sign differently; do not mix them up.
Payout API std1: HMAC-SHA256
- Take every parameter (every field when verifying a response or callback) except
sign. - Leave out empty strings (an empty string means “not sent”);
0and a space are signed. - Sort the names by UTF-8 bytes, ascending: case-sensitive, uppercase before lowercase, and a numeric name
20before3. - Join
name=valuepairs with&. Use the raw values: no URL encoding, no trimming, no HTML entities. - Compute HMAC-SHA256 with your API key. The key is the text shown under “Security” in the merchant portal itself (a 64-character hex string for new merchants), used as its UTF-8 bytes; never hex-decode it into bytes.
- Write the result as 64 lowercase hex characters, keeping leading zeros. We compare signatures case-insensitively.
Requests, responses and callbacks use the same rule and the same key. Extra parameters you add that are not documented must be signed too (we ignore them after verifying).
Examples
Example key (fake)
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Use the key text's UTF-8 bytes as the HMAC key; never hex-decode it. hexDecodedKeySign is the WRONG result you get by decoding.
Parameters
[["mchId","DEMO0001"],["mchOrderNo","ORDER-20260917-0001"],["amount","10000"]]
String to sign
amount=10000&mchId=DEMO0001&mchOrderNo=ORDER-20260917-0001
sign
9baf546e1f95057438cab0379a8abe79f65110f4edb21ae3c1131b7b87694509
Wrong way (hex-decoding the key into 32 bytes first) gives
b21db2c6def6135e35d9fca688449a9bb1a14b226be5c21adff1b267a6c3a1bb
Empty strings and nulls are left out; "0" and a single space are signed; sign itself is left out
Parameters
[["a","1"],["b",""],["c",null],["d"," "],["e","0"],["sign","ffff"]]
String to sign
a=1&d= &e=0
sign
e73689ddfba7cb0e1fee38f7450ced153080e6e9ec2e29b986a6f5259dc6ef09
Names sort by UTF-8 bytes, case-sensitive: digits < upper case < underscore < lower case; numeric names compare as bytes too
Parameters
[["b","1"],["a","2"],["B","3"],["_","4"],["0","5"],["20","6"],["3","7"],["aB","8"],["a_b","9"],["a1","10"],["Z","11"],["ab","12"]]
String to sign
0=5&20=6&3=7&B=3&Z=11&_=4&a=2&a1=10&aB=8&a_b=9&ab=12&b=1
sign
cc98598fb90d59b6603d65a813ad54959ce38cdbe6e1fb2ed1c0b7baead5cf3d
The signature starts with 00: hex output must keep leading zeros (64 chars)
Parameters
[["mchId","DEMO0001"],["nonce","112"]]
String to sign
mchId=DEMO0001&nonce=112
sign
00e9a48bd200689f0a7e7a91e650f022f7e54d7f6816d869ddce36f19c1e5c1a
Verifying responses and callbacks
- Responses: take every field of the response JSON except
sign, compute as above and compare withsign(ignoring case). Treat a response that fails verification as “result unknown” and trust none of its fields. - Callbacks: verify with the parameters in the POST body only. Do not mix in the query string of your callback URL: many frameworks merge both into one parameter map, and a merged map never verifies.
Collection API compat1 (legacy): MD5
Exactly as in the old system:
- Take every parameter received except
sign; leave out null values and values that are exactly the empty string. A value that is only spaces is signed. - A value that is a JSON object (only
payParamsin responses) is written as compact JSON (no spaces) with its first-level keys sorted by character code, and that text is the value. - Build
name=valuepairs and sort the wholename=valuestrings case-insensitively. Note this is not a sort by name alone:param10=…comes beforeparam1=…because0sorts before=. - Join with
&and append&key=your merchant private key. - MD5 the UTF-8 bytes and write 32 uppercase hex characters. We compare case-insensitively.
- Every non-empty parameter you send is signed, including undocumented ones.
Examples
Create order request:
Example key (fake):demo-key-NOT-REAL-do-not-use-0001
Parameters
mchId=20000001
productId=8001
mchOrderNo=ORDER20260918001
amount=10000
notifyUrl=https://merchant.example.com/notify
subject=goods
body=goods
param1=shop-7
param2=张三
reqTime=20260918120000
String to sign
amount=10000&body=goods&mchId=20000001&mchOrderNo=ORDER20260918001¬ifyUrl=https://merchant.example.com/notify¶m1=shop-7¶m2=张三&productId=8001&reqTime=20260918120000&subject=goods&key=demo-key-NOT-REAL-do-not-use-0001
sign
86032564C62F16F220F9DBAD7D88C690
Request body
mchId=20000001&productId=8001&mchOrderNo=ORDER20260918001&amount=10000¬ifyUrl=https%3A%2F%2Fmerchant.example.com%2Fnotify&subject=goods&body=goods¶m1=shop-7¶m2=%E5%BC%A0%E4%B8%89&reqTime=20260918120000&sign=86032564C62F16F220F9DBAD7D88C690Callback (sign is in the body; empty values are not signed):
Example key (fake):demo-key-NOT-REAL-do-not-use-0001
Your notifyUrl
https://merchant.example.com/notify
Sent to
https://merchant.example.com/notify
Callback body
income=10000&payOrderId=ORDER20260918001&amount=10000&mchId=20000001&productId=8001&mchOrderNo=ORDER20260918001&paySuccTime=1789704005000&sign=9447B44CF966CB47A625CADF63C539A8&channelOrderNo=UP20260918000123&backType=2¶m1=shop-7¶m2=%E5%BC%A0%E4%B8%89&channelAttach=&status=2
String to sign
amount=10000&backType=2&channelOrderNo=UP20260918000123&income=10000&mchId=20000001&mchOrderNo=ORDER20260918001¶m1=shop-7¶m2=张三&payOrderId=ORDER20260918001&paySuccTime=1789704005000&productId=8001&status=2&key=demo-key-NOT-REAL-do-not-use-0001