Skip to main content

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 formulasWhat it is
$PASSWORDYour 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.
$identifierThe identifier request parameter: token, account information or additional descriptor. If you do not send it, leave the term out of the hash input.
$trans_idThe 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 callbackSignature
SALESale signature
DEBIT2VIRTUAL, DEBIT2VIRTUAL_CALCDebit signature
DEBIT2VIRTUAL_COMPLETEComplete Debit signature
CAPTURECapture signature
VOIDVoid signature
CREDITVOIDCreditvoid signature
GET_TRANS_STATUSGET_TRANS_STATUS signature
CREDIT2VIRTUALCredit2Virtual signature
CREDIT2CRYPTOCredit2Crypto signature
every callback on a SALE or DEBIT2VIRTUAL payment, including its CAPTURE, VOID and CREDITVOID callbacksCallback signature, worked example in Sale callback signature
CREDIT2VIRTUAL and CREDIT2CRYPTO callbacksCredit2Virtual callback signature
Two shapes that look alike but are not

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
ParameterExample value
identifierbrand-identifier
order_idorder-1234
order_amount10.00
order_currencyUSD
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
passwordm3rch4ntP4ss

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
ParameterExample value
order_idorder-1234
order_amount10.00
order_currencyUSD
passwordm3rch4ntP4ss

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
ParameterExample value
order_idorder-1234
order_amount10.00
order_currencyUSD
passwordm3rch4ntP4ss

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
ParameterExample value
identifierbrand-identifier
order_idorder-1234
order_amount10.00
order_currencyUSD
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
passwordm3rch4ntP4ss

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
ParameterExample value
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
order_idorder-1234
statusSUCCESS
passwordm3rch4ntP4ss

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)