Appendix A
Appendix A
The hash is the signature Payment Platform uses to validate the requests your system sends, and that your system uses to validate the callbacks Payment Platform sends back. By default it is an MD5 digest, calculated per action with the formulas on this page.
These formulas are specific to S2S APM. They are not the Checkout recipe: there is no outer sha1 pass. For the same information laid out per protocol, and for Checkout and S2S CARD, see Hash signature.
Every formula uppercases its assembled input with PHP strtoupper, which changes only a-z. If a value that goes into a hash can contain characters outside ASCII, see Uppercasing and non-ASCII values.
The digest is MD5 by default and SHA256 when the Use SHA256 encryption algorithm for hash option is enabled for your protocol mapping. The inputs and the order below do not change; only the digest function does. The setting, and what it does to every protocol, is described once under Digest mode.
Notation used in the formulas
| In the formulas | What it is |
|---|---|
$PASSWORD | Your merchant password, the Password field of the merchant in the admin panel. It is not the client_key, and it is never sent in the request - it only goes into the hash input. |
$identifier | The identifier request parameter: token, account information or additional descriptor. If you do not send it, leave the term out of the hash input. |
$trans_id | The transaction id in Payment Platform, the trans_id you send in the call or receive in the callback. |
. | String concatenation. Nothing is inserted between the values - no separator, no space. |
Which signature for which action
| Action or callback | Signature |
|---|---|
SALE | Sale signature |
DEBIT2VIRTUAL, DEBIT2VIRTUAL_CALC | Debit signature |
DEBIT2VIRTUAL_COMPLETE | Complete Debit signature |
CAPTURE | Capture signature |
VOID | Void signature |
CREDITVOID | Creditvoid signature |
GET_TRANS_STATUS | GET_TRANS_STATUS signature |
CREDIT2VIRTUAL | Credit2Virtual signature |
CREDIT2CRYPTO | Credit2Crypto signature |
every callback on a SALE or DEBIT2VIRTUAL payment, including its CAPTURE, VOID and CREDITVOID callbacks | Callback signature, worked example in Sale callback signature |
CREDIT2VIRTUAL and CREDIT2CRYPTO callbacks | Credit2Virtual callback signature |
strrev($trans_id . $PASSWORD) and strrev($trans_id) . $PASSWORD produce completely different strings. Creditvoid and Complete Debit reverse the transaction id and the password together. Capture, Void and GET_TRANS_STATUS reverse only the transaction id and then append the password as it is. Check which one your helper implements before you conclude that the password is wrong.
Sale signature
Used for the SALE request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($identifier . $order_id . $order_amount . $order_currency . $PASSWORD)));
Worked example
| Parameter | Example value |
|---|---|
identifier | brand-identifier |
order_id | order-1234 |
order_amount | 10.00 |
order_currency | USD |
password | m3rch4ntP4ss |
The string that gets hashed:
SS4PTN4HCR3MDSU00.014321-REDROREIFITNEDI-DNARB
The resulting hash:
c0902af3bab3d3b14ff20d11808d7f4b
PHP
<?php
$identifier = "brand-identifier";
$order_id = "order-1234";
$order_amount = "10.00";
$order_currency = "USD";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($identifier . $order_id . $order_amount . $order_currency . $password));
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const identifier = "brand-identifier";
const order_id = "order-1234";
const order_amount = "10.00";
const order_currency = "USD";
const password = "m3rch4ntP4ss";
const raw = rev(identifier + order_id + order_amount + order_currency + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
identifier = "brand-identifier"
order_id = "order-1234"
order_amount = "10.00"
order_currency = "USD"
password = "m3rch4ntP4ss"
raw = (identifier + order_id + order_amount + order_currency + password)[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Capture signature
Used for the CAPTURE request that settles a SALE sent with auth=Y.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
password | m3rch4ntP4ss |
The string that gets hashed:
D6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1m3rch4ntP4ss
The resulting hash:
d868b91ebb3652361ad1f361aaf61530
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
password = "m3rch4ntP4ss"
raw = trans_id[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Creditvoid signature
Used for the CREDITVOID request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id . $PASSWORD)));
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
password | m3rch4ntP4ss |
The string that gets hashed:
SS4PTN4HCR3MD6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1
The resulting hash:
0276834e2973442605ea728271bc4918
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id . $password));
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
password = "m3rch4ntP4ss"
raw = (trans_id + password)[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Void signature
Used for the VOID request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
password | m3rch4ntP4ss |
The string that gets hashed:
D6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1m3rch4ntP4ss
The resulting hash:
d868b91ebb3652361ad1f361aaf61530
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
password = "m3rch4ntP4ss"
raw = trans_id[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Credit2Virtual signature
Used for the CREDIT2VIRTUAL request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($order_id . $order_amount . $order_currency)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
order_id | order-1234 |
order_amount | 10.00 |
order_currency | USD |
password | m3rch4ntP4ss |
The string that gets hashed:
DSU00.014321-REDROm3rch4ntP4ss
The resulting hash:
7e726b491a59c55ca3fe18fba457c3ee
PHP
<?php
$order_id = "order-1234";
$order_amount = "10.00";
$order_currency = "USD";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($order_id . $order_amount . $order_currency)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const order_id = "order-1234";
const order_amount = "10.00";
const order_currency = "USD";
const password = "m3rch4ntP4ss";
const raw = rev(order_id + order_amount + order_currency).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
order_id = "order-1234"
order_amount = "10.00"
order_currency = "USD"
password = "m3rch4ntP4ss"
raw = (order_id + order_amount + order_currency)[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Credit2Crypto signature
Used for the CREDIT2CRYPTO request. The inputs and the order are the same as for the Credit2Virtual signature.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($order_id . $order_amount . $order_currency)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
order_id | order-1234 |
order_amount | 10.00 |
order_currency | USD |
password | m3rch4ntP4ss |
The string that gets hashed:
DSU00.014321-REDROm3rch4ntP4ss
The resulting hash:
7e726b491a59c55ca3fe18fba457c3ee
PHP
<?php
$order_id = "order-1234";
$order_amount = "10.00";
$order_currency = "USD";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($order_id . $order_amount . $order_currency)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const order_id = "order-1234";
const order_amount = "10.00";
const order_currency = "USD";
const password = "m3rch4ntP4ss";
const raw = rev(order_id + order_amount + order_currency).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
order_id = "order-1234"
order_amount = "10.00"
order_currency = "USD"
password = "m3rch4ntP4ss"
raw = (order_id + order_amount + order_currency)[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Debit signature
Used for the DEBIT2VIRTUAL and DEBIT2VIRTUAL_CALC requests.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($identifier . $order_id . $order_amount . $order_currency . $PASSWORD)));
Worked example
| Parameter | Example value |
|---|---|
identifier | brand-identifier |
order_id | order-1234 |
order_amount | 10.00 |
order_currency | USD |
password | m3rch4ntP4ss |
The string that gets hashed:
SS4PTN4HCR3MDSU00.014321-REDROREIFITNEDI-DNARB
The resulting hash:
c0902af3bab3d3b14ff20d11808d7f4b
PHP
<?php
$identifier = "brand-identifier";
$order_id = "order-1234";
$order_amount = "10.00";
$order_currency = "USD";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($identifier . $order_id . $order_amount . $order_currency . $password));
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const identifier = "brand-identifier";
const order_id = "order-1234";
const order_amount = "10.00";
const order_currency = "USD";
const password = "m3rch4ntP4ss";
const raw = rev(identifier + order_id + order_amount + order_currency + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
identifier = "brand-identifier"
order_id = "order-1234"
order_amount = "10.00"
order_currency = "USD"
password = "m3rch4ntP4ss"
raw = (identifier + order_id + order_amount + order_currency + password)[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Complete Debit signature
Used for the DEBIT2VIRTUAL_COMPLETE request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id . $PASSWORD)));
trans_id and PASSWORD are concatenated first and reversed together, the same shape as the Creditvoid signature. This is not the same as the Capture signature, which reverses trans_id alone.
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
password | m3rch4ntP4ss |
The string that gets hashed:
SS4PTN4HCR3MD6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1
The resulting hash:
0276834e2973442605ea728271bc4918
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id . $password));
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
password = "m3rch4ntP4ss"
raw = (trans_id + password)[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
GET_TRANS_STATUS signature
Used for the GET_TRANS_STATUS request.
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
password | m3rch4ntP4ss |
The string that gets hashed:
D6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1m3rch4ntP4ss
The resulting hash:
d868b91ebb3652361ad1f361aaf61530
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
password = "m3rch4ntP4ss"
raw = trans_id[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Callback signature
Used for every callback sent for a SALE or a DEBIT2VIRTUAL payment, not only the first one. The CAPTURE, VOID and CREDITVOID callbacks on that payment are signed the same way, because the rule is chosen by the payment (a virtual purchase or debit), not by the operation you just sent. There is no fixed field list: the signature is built from whatever the callback body contains. A worked example is in Sale callback signature.
Hash is calculated by the formula:
// $params = the callback body, decoded, without the `hash` key.
array_walk_recursive($params, static function (&$value) {
$value = strrev((string) $value);
});
$hash = md5(strtoupper(convert($params) . $PASSWORD));
function convert(array $params): string
{
foreach ($params as &$value) {
if (is_array($value)) {
$value = convert($value);
}
}
ksort($params);
return implode('', $params);
}
Worked example, runnable
The values below are the ones from the example above, with PASSWORD replaced by a real value. Nested objects are sorted at every level.
The string that gets hashed:
ELAS22.9SSECCUS23321523M3RCH4NTP4SS
The resulting hash:
3ff7a6571cfa1d5a7952750faab8c995
PHP
<?php
$password = "m3rch4ntP4ss";
$params = [
"action" => "SALE",
"result" => "SUCCESS",
"amount" => "9.22",
"transactions" => ["ctrans1" => "123", "atrans2" => "32", "itrans2" => "325"],
];
function flatten(array $node): string {
ksort($node);
$out = "";
foreach ($node as $value) {
$out .= is_array($value) ? flatten($value) : strrev((string) $value);
}
return $out;
}
$raw = strtoupper(flatten($params)) . strtoupper($password);
$hash = md5($raw);
echo $raw . "\n" . $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const password = "m3rch4ntP4ss";
const params = {
action: "SALE",
result: "SUCCESS",
amount: "9.22",
transactions: { ctrans1: "123", atrans2: "32", itrans2: "325" },
};
const rev = s => String(s).split("").reverse().join("");
function flatten(node) {
return Object.keys(node).sort().reduce(
(acc, k) => acc + (typeof node[k] === "object" ? flatten(node[k]) : rev(node[k])), "");
}
const raw = flatten(params).toUpperCase() + password.toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(raw);
console.log(hash);
Python
import hashlib
password = "m3rch4ntP4ss"
params = {
"action": "SALE",
"result": "SUCCESS",
"amount": "9.22",
"transactions": {"ctrans1": "123", "atrans2": "32", "itrans2": "325"},
}
def flatten(node):
# sort by parameter name at every level, reverse each value, concatenate
out = ""
for key in sorted(node):
value = node[key]
out += flatten(value) if isinstance(value, dict) else str(value)[::-1]
return out
raw = flatten(params).upper() + password.upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(raw)
print(hash_value)
Sale callback signature
The same rule as the Callback signature above, written out step by step with a worked example.
Hash calculation for notification in S2S APM based on the next formula:
• All parameter values from the callback are used, except for hash;
• Sort values alphabetically by their parameter names;
• Reverse each value individually;
• Concatenate all reversed values together into a single string;
• Convert this string to uppercase;
• Append the password in uppercase to the end of this string;
• Generate the MD5 hash of the resulting string
For example, for callback like this:
action=SALE
result=SUCCESS
amount=9.22
transactions=[ctrans1 = 123, atrans2 =32, itrans2 =325]
formula will be looking like this:
hash = reverse(action) + reverse(amount) + reverse(result) + reverse(transactions.atrans2) + reverse(transactions.ctrans1) + reverse(transactions.itrans2) + PASSWORD
and string_result:
string_result = ELAS22.9SSECCUS23321523PASSWORD
and hash:
hash = md5(string_result)
Credit2Virtual callback signature
Hash is calculated by the formula:
$hash = md5(strtoupper(strrev($trans_id . $order_id . $status)) . $PASSWORD);
Worked example
| Parameter | Example value |
|---|---|
trans_id | 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d |
order_id | order-1234 |
status | SUCCESS |
password | m3rch4ntP4ss |
The string that gets hashed:
SSECCUS4321-REDROD6C5B4A3F2E1-D0C9-B8A7-F6E5-D4C3B2A1m3rch4ntP4ss
The resulting hash:
9c17f512df0a78c90f064ebf9fe0312a
PHP
<?php
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$order_id = "order-1234";
$status = "SUCCESS";
$password = "m3rch4ntP4ss";
$raw = strtoupper(strrev($trans_id . $order_id . $status)) . $password;
$hash = md5($raw);
echo $hash;
JavaScript (Node.js)
const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const order_id = "order-1234";
const status = "SUCCESS";
const password = "m3rch4ntP4ss";
const raw = rev(trans_id + order_id + status).toUpperCase() + password;
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);
Python
import hashlib
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
order_id = "order-1234"
status = "SUCCESS"
password = "m3rch4ntP4ss"
raw = (trans_id + order_id + status)[::-1].upper() + password
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)