Signature
During the payment process, signatures are used to ensure the security and integrity of transaction requests and responses. Merchants use secret keys to sign transaction data, preventing data tampering during transmission.
Signature Algorithm: SHA-256
Signature Process
Obtain the Merchant Secret Key: Log in to the Onerway dashboard → Click "Account" → "Account Info" → Retrieve the "Secret key" value from the page.
Prepare Data for Signing: Include all non-empty request parameters except
signitself. Sort by parameter name inASCIIascending order, concatenate values asvalue1value2value3..., then append the merchant secret key.Generate Signature: Apply the
SHA-256algorithm to the concatenated string
Unified Signing/Verification Rules
- Request signing and webhook verification use the same algorithm:
ASCII sort -> concatenate values -> append secretKey -> SHA-256. - Use a simple default for request signing: exclude only
sign, and include all other non-empty fields (that is,REQUEST_EXCLUDED_KEYS = ['sign']). - Webhook verification must dynamically exclude fields marked
Noin the Signature column. Do not hardcode aYeswhitelist. - Treat
originTransactionIdas included by default; exclude it only in webhook verification.
Current Webhook Excluded Fields
'originTransactionId', 'originMerchantTxnId', 'customsDeclarationAmount', 'customsDeclarationCurrency', 'paymentMethod', 'walletTypeName', 'periodValue', 'tokenExpireTime', 'sign'
Signature Example
// Unsorted Original Parameters
{
"test3": "test3value",
"test1": "0",
"test5": "",
"test4": "test4value",
"test6": null
}2
3
4
5
6
7
8
1. Remove Empty Fields and sign
Remove test5 (empty string) and test6 (null):
{
"test1": "0",
"test3": "test3value",
"test5": "",
"test4": "test4value",
"test6": null
}2
3
4
5
6
7
2. Sort Parameters by ASCII Code
{
"test1": "0",
"test3": "test3value",
"test4": "test4value",
"test5": "",
"test6": null
}2
3
4
5
6
7
3. Concatenate Parameter Values
0test3valuetest4value
4. Add Merchant Secret Key
Secret Key: 3b5e10b65bff4172a5b9ca2d2ec00a6e
Concatenated String: 0test3valuetest4value3b5e10b65bff4172a5b9ca2d2ec00a6e
5. Generate SHA-256 Signature
836831ae68fce5e61d4a64363bb72c636efe6e94b62ecaa8d1c95fc58cc9cbed
Signature Implementation Examples
Signature Implementation Notes
- Data Type Conversion: When signing, all values must be strings
- Object Handling: If a value is an
object, convert it to a string first. See thebillingInformationandshippingInformationfields below - Nested Object Handling: If a value contains nested objects, convert the inner object to a string first, then convert the outer object. See the
txnOrderMsgfield below, where theproductsfield is a string converted from an array
Using the Checkout Payment request parameters as an example:
{
"billingInformation": "{\"country\":\"DE\",\"email\":\"abel.wang@onerway.com\",\"firstName\":\"şş\",\"lastName\":\"café\",\"phone\":\"17700492982\",\"address\":\"Apt. 870\",\"city\":\"Akşehir\",\"postalCode\":\"66977\",\"identityNumber\":\"12345678\",\"province\":\"Akşehir\"}",
"merchantCustId": "1723097638000",
"merchantNo": "800209",
"merchantTxnId": "1723097638000",
"merchantTxnTime": "2024-08-08 14:13:58",
"merchantTxnTimeZone": "+08:00",
"orderAmount": "100",
"orderCurrency": "USD",
"productType": "CARD",
"shippingInformation": "{\"country\":\"DE\",\"email\":\"abel.wang@onerway.com\",\"firstName\":\"şş\",\"lastName\":\"café\",\"phone\":\"17700492982\",\"address\":\"Apt. 870\",\"city\":\"Akşehir\",\"postalCode\":\"66977\",\"identityNumber\":\"12345678\",\"province\":\"Akşehir\"}",
"sign": "d23532edf2d8d4c6cb21b622bfbef4f067ef9ac2796e89117578037d11b04e98",
"subProductType": "DIRECT",
"txnOrderMsg": "{\"products\":\"[{\\\"price\\\":\\\"110.00\\\",\\\"num\\\":\\\"1\\\",\\\"name\\\":\\\"iphone11\\\",\\\"currency\\\":\\\"USD\\\"}]\",\"returnUrl\":\"https://docs.onerway.com/\",\"transactionIp\":\"127.0.0.1\",\"appId\":\"1739545982264549376\",\"javaEnabled\":false,\"colorDepth\":\"24\",\"screenHeight\":\"1080\",\"screenWidth\":\"1920\",\"timeZoneOffset\":\"-480\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\",\"contentLength\":\"340\",\"language\":\"zh-CN\"}",
"txnType": "SALE"
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
const REQUEST_EXCLUDED_KEYS = ['sign'];
function buildCanonicalString(array $payload, array $excludedKeys): string
{
ksort($payload, SORT_STRING);
$canonicalString = '';
foreach ($payload as $key => $value) {
if (in_array($key, $excludedKeys, true)) {
continue;
}
if ($value === null || $value === '') {
continue;
}
$canonicalString .= (string) $value;
}
return $canonicalString;
}
function generateSignature(
array $payload,
string $secretKey,
array $excludedKeys
): string {
$canonicalString = buildCanonicalString($payload, $excludedKeys);
return hash('sha256', $canonicalString . $secretKey);
}
$requestPayload = [
'merchantNo' => '800209',
'originTransactionId' => '1925119178365603842',
'refundAmount' => '45',
'sign' => ''
];
$secretKey = 'your-secret-key';
$signature = generateSignature($requestPayload, $secretKey, REQUEST_EXCLUDED_KEYS);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@Slf4j
public class HashUtil {
private static final Set<String> REQUEST_EXCLUDED_KEYS = Set.of("sign");
public static void main(String[] args) {
TreeMap<String, Object> payload = new TreeMap<>();
payload.put("merchantNo", "800209");
payload.put("originTransactionId", "1925119178365603842");
payload.put("refundAmount", "45");
payload.put("sign", "");
// TODO: Replace with your own merchant secret key
String secretKey = "Your Secret Key";
String signature = generateSignature(payload, secretKey, REQUEST_EXCLUDED_KEYS);
System.out.println("Signature = " + signature);
}
/**
* Remove empty values and excluded keys, then concatenate values after ASCII sorting
*
* @param payload Request or webhook payload
* @param excludedKeys Keys to exclude
* @return Concatenated string
*/
public static String buildCanonicalString(
Map<String, Object> payload,
Set<String> excludedKeys
) {
TreeMap<String, Object> sortedPayload = new TreeMap<>(payload);
StringBuilder canonicalString = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedPayload.entrySet()) {
if (excludedKeys.contains(entry.getKey())) {
continue;
}
Object value = entry.getValue();
if (value == null || "".equals(value)) {
continue;
}
canonicalString.append(value);
}
return canonicalString.toString();
}
public static String generateSignature(
Map<String, Object> payload,
String secretKey,
Set<String> excludedKeys
) {
String canonicalString = buildCanonicalString(payload, excludedKeys);
String dataToSign = canonicalString + secretKey;
final String algorithm = "SHA-256";
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(dataToSign.getBytes(StandardCharsets.UTF_8));
return byte2Hex(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 not available", e);
}
}
/**
* Convert byte array to hexadecimal string
*
* @param bytes Byte array to convert
* @return Hexadecimal string representation
*/
public static String byte2Hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte value : bytes) {
String hexValue = Integer.toHexString(value & 0xFF);
if (hexValue.length() == 1) {
sb.append("0");
}
sb.append(hexValue);
}
return sb.toString();
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import hashlib
REQUEST_EXCLUDED_KEYS = {"sign"}
def build_canonical_string(payload: dict, excluded_keys: set[str]) -> str:
canonical_parts = []
for key in sorted(payload.keys()):
if key in excluded_keys:
continue
value = payload[key]
if value is None or value == "":
continue
canonical_parts.append(str(value))
return "".join(canonical_parts)
def generate_signature(payload: dict, secret_key: str, excluded_keys: set[str]) -> str:
canonical_string = build_canonical_string(payload, excluded_keys)
data_to_sign = f"{canonical_string}{secret_key}"
return hashlib.sha256(data_to_sign.encode("utf-8")).hexdigest()
if __name__ == "__main__":
payload = {
"merchantNo": "800209",
"originTransactionId": "1925119178365603842",
"refundAmount": "45",
"sign": "",
}
secret_key = "your-secret-key"
signature = generate_signature(payload, secret_key, REQUEST_EXCLUDED_KEYS)
print("Signature =", signature)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39