Skip to main content

Appendix A

Hash formulas

The hash field is the signature that Payment Platform uses to validate requests from your system, and that your system uses to validate callbacks from Payment Platform. Calculate the hash value with MD5 using the per-action formulas below.

These formulas are specific to S2S CARD. 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 APM, 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.

SHA256 hash mode

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
emailThe payer email for the transaction: the payer_email you send in the request, or the one Payment Platform stored for the initiating payment when the call carries only trans_id.
PASSWORDYour merchant password, the Password field of the merchant in the admin panel. It is not the client_key and not the merchant key, and it is never sent in the request - it only goes into the hash input.
card_numberThe PAN the card fragment is taken from. Only the first 6 and the last 4 digits take part. On calls that carry only trans_id, the fragment is the one Payment Platform stored for that transaction, returned to you in the card parameter.

Which formula for which action

Action or callbackFormula
SALE, SALE with auth=Y, DEBIT, CARD2CARDFormula 1
SALE, DEBIT sent with payment_token (digital wallet)Formula 8
RECURRING_SALE, RECURRING_DEBITFormula 1
CAPTURE, VOID, CREDITVOID, RETRY, GET_TRANS_STATUS, GET_TRANS_DETAILSFormula 2
the same actions on a transaction that has no payer email, for example a CREDIT2CARD payoutFormula 6
the same actions on a transaction that has no card data storedno card data on file
GET_TRANS_STATUS_BY_ORDERFormula 7
CREATE_SCHEDULEFormula 3
UPDATE_SCHEDULE, PAUSE_SCHEDULE, RUN_SCHEDULE, DELETE_SCHEDULE, SCHEDULE_INFO, DESCHEDULEFormula 4
CREDIT2CARD requestFormula 5
CREDIT2CARD callbackFormula 6
every other callbackFormula 2
Optional parameters you do not send

If a formula contains a parameter that is optional and you do not send it - payer_email in a DEBIT request, for example - leave that term out of the hash input entirely. Do not put an empty placeholder in its place and do not keep a separator for it. Formula 6 and Formula 8 are this rule already applied to the two cases you are most likely to meet.

Formula 1

Used for SALE, SALE with auth=Y, DEBIT, CARD2CARD, RECURRING_SALE and RECURRING_DEBIT.

hash for SALE is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD.strrev(substr(card_number,0,6).substr(card_number,-4))))

if parameter card_token is specified hash is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD.strrev(card_token)))

hash for RECURRING_SALE is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD.strrev(substr(card_number,0,6).substr(card_number,-4))))

For RECURRING_SALE and RECURRING_DEBIT, Payment Platform takes email and the card fragment from the stored initiating payment, not from the parameters you send in that call.

For CARD2CARD, the card fragment is taken from the sender card, that is from payer_card_number, and never from payee_card_number.

Worked example
ParameterExample value
email[email protected]
passwordm3rch4ntP4ss
card_number4111111111111111

The string that gets hashed:

MOC.ELPMAXE@NHOJM3RCH4NTP4SS1111111114

The resulting hash:

adff769832661477ef1e2d6393b57546

PHP

<?php
$email = "[email protected]";
$password = "m3rch4ntP4ss";
$card_number = "4111111111111111";

$raw = strtoupper(strrev($email) . $password . strrev(substr($card_number,0,6) . substr($card_number,-4)));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const email = "[email protected]";
const password = "m3rch4ntP4ss";
const card_number = "4111111111111111";

const raw = (rev(email) + password + rev(card_number.slice(0,6) + card_number.slice(-4))).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

email = "[email protected]"
password = "m3rch4ntP4ss"
card_number = "4111111111111111"

raw = (email[::-1] + password + (card_number[:6] + card_number[-4:])[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Worked example
ParameterExample value
email[email protected]
passwordm3rch4ntP4ss
card_token9b74c9897bac770ffc029102a200c5de

The string that gets hashed:

MOC.ELPMAXE@NHOJM3RCH4NTP4SSED5C002A201920CFF077CAB7989C47B9

The resulting hash:

7dd38ef934c425bafb3ff1b9346f0142

PHP

<?php
$email = "[email protected]";
$password = "m3rch4ntP4ss";
$card_token = "9b74c9897bac770ffc029102a200c5de";

$raw = strtoupper(strrev($email) . $password . strrev($card_token));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const email = "[email protected]";
const password = "m3rch4ntP4ss";
const card_token = "9b74c9897bac770ffc029102a200c5de";

const raw = (rev(email) + password + rev(card_token)).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

email = "[email protected]"
password = "m3rch4ntP4ss"
card_token = "9b74c9897bac770ffc029102a200c5de"

raw = (email[::-1] + password + card_token[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 2

Used for CAPTURE, VOID, CREDITVOID, RETRY, GET_TRANS_STATUS and GET_TRANS_DETAILS, and for every callback except the CREDIT2CARD callback.

RETRY sends only trans_id, so it is signed with this formula and not with Formula 1: the email and the card fragment come from the stored initiating payment.

hash is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD.trans_id.strrev(substr(card_number,0,6).substr(card_number,-4))))

The card fragment is the one Payment Platform stored for that transaction, not card data you send in the call. In a callback, trans_id is the trans_id of the payment. If the transaction has no payer email, use Formula 6; if it has no card data stored at all, see no card data on file - which applies to every action listed here except CREDITVOID.

Worked example
ParameterExample value
email[email protected]
passwordm3rch4ntP4ss
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
card_number4111111111111111

The string that gets hashed:

MOC.ELPMAXE@NHOJM3RCH4NTP4SS1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D1111111114

The resulting hash:

08e0d6cf105fb585e5bfacb0852f6ab3

PHP

<?php
$email = "[email protected]";
$password = "m3rch4ntP4ss";
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$card_number = "4111111111111111";

$raw = strtoupper(strrev($email) . $password . $trans_id . strrev(substr($card_number,0,6) . substr($card_number,-4)));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const email = "[email protected]";
const password = "m3rch4ntP4ss";
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const card_number = "4111111111111111";

const raw = (rev(email) + password + trans_id + rev(card_number.slice(0,6) + card_number.slice(-4))).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

email = "[email protected]"
password = "m3rch4ntP4ss"
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
card_number = "4111111111111111"

raw = (email[::-1] + password + trans_id + (card_number[:6] + card_number[-4:])[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 3

Used for CREATE_SCHEDULE.

hash for Create a schedule is calculated by the formula:

md5(strtoupper(strrev(PASSWORD)))

Worked example
ParameterExample value
passwordm3rch4ntP4ss

The string that gets hashed:

SS4PTN4HCR3M

The resulting hash:

35b7c8b50a964eca3fcd89ab6386d711

PHP

<?php
$password = "m3rch4ntP4ss";

$raw = strtoupper(strrev($password));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const password = "m3rch4ntP4ss";

const raw = rev(password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

password = "m3rch4ntP4ss"

raw = password[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 4

Used for UPDATE_SCHEDULE, PAUSE_SCHEDULE, RUN_SCHEDULE, DELETE_SCHEDULE, SCHEDULE_INFO and DESCHEDULE.

hash for Other schedules is calculated by the formula:

md5(strtoupper(strrev(schedule_id + PASSWORD)))

schedule_id and PASSWORD are concatenated first and reversed together, so this is not the same as reversing schedule_id and then appending PASSWORD.

Worked example
ParameterExample value
schedule_id7f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0
passwordm3rch4ntP4ss

The string that gets hashed:

SS4PTN4HCR3M0F1E2D3C4B5A-6978-8796-A5B4-C3D2E1F7

The resulting hash:

cf82f235dcb2dcc178e248fb746a9eab

PHP

<?php
$schedule_id = "7f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0";
$password = "m3rch4ntP4ss";

$raw = strtoupper(strrev($schedule_id . $password));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const schedule_id = "7f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0";
const password = "m3rch4ntP4ss";

const raw = rev(schedule_id + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

schedule_id = "7f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0"
password = "m3rch4ntP4ss"

raw = (schedule_id + password)[::-1].upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 5

Used for the CREDIT2CARD request.

hash for CREDIT2CARD request is calculated by the formula:

md5(strtoupper(PASSWORD.strrev(substr(card_number,0,6).substr(card_number,-4))))

if card_token is specified hash is calculated by the formula:

md5(strtoupper(PASSWORD. strrev(card_token)))

Worked example
ParameterExample value
passwordm3rch4ntP4ss
card_number4111111111111111

The string that gets hashed:

M3RCH4NTP4SS1111111114

The resulting hash:

eb43518dd1b49c127457ac142044f454

PHP

<?php
$password = "m3rch4ntP4ss";
$card_number = "4111111111111111";

$raw = strtoupper($password . strrev(substr($card_number,0,6) . substr($card_number,-4)));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const password = "m3rch4ntP4ss";
const card_number = "4111111111111111";

const raw = (password + rev(card_number.slice(0,6) + card_number.slice(-4))).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

password = "m3rch4ntP4ss"
card_number = "4111111111111111"

raw = (password + (card_number[:6] + card_number[-4:])[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)
Worked example
ParameterExample value
passwordm3rch4ntP4ss
card_token9b74c9897bac770ffc029102a200c5de

The string that gets hashed:

M3RCH4NTP4SSED5C002A201920CFF077CAB7989C47B9

The resulting hash:

3093f20bad8e2e05d80237b8a5789627

PHP

<?php
$password = "m3rch4ntP4ss";
$card_token = "9b74c9897bac770ffc029102a200c5de";

$raw = strtoupper($password . strrev($card_token));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const password = "m3rch4ntP4ss";
const card_token = "9b74c9897bac770ffc029102a200c5de";

const raw = (password + rev(card_token)).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

password = "m3rch4ntP4ss"
card_token = "9b74c9897bac770ffc029102a200c5de"

raw = (password + card_token[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 6

Used for the CREDIT2CARD callback, and for CAPTURE, VOID, CREDITVOID, GET_TRANS_STATUS and GET_TRANS_DETAILS on a transaction that has no payer email - a CREDIT2CARD payout, for example, carries payee_email and no payer email.

hash is calculated by the formula:

md5(strtoupper(PASSWORD.trans_id.strrev(substr(card_number,0,6).substr(card_number,-4))))

This is Formula 2 without the strrev(email) term, following the Pay attention rule above.

Worked example
ParameterExample value
passwordm3rch4ntP4ss
trans_id1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
card_number4111111111111111

The string that gets hashed:

M3RCH4NTP4SS1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D1111111114

The resulting hash:

349bd331411b799a74578c660323107a

PHP

<?php
$password = "m3rch4ntP4ss";
$trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
$card_number = "4111111111111111";

$raw = strtoupper($password . $trans_id . strrev(substr($card_number,0,6) . substr($card_number,-4)));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const password = "m3rch4ntP4ss";
const trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
const card_number = "4111111111111111";

const raw = (password + trans_id + rev(card_number.slice(0,6) + card_number.slice(-4))).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

password = "m3rch4ntP4ss"
trans_id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
card_number = "4111111111111111"

raw = (password + trans_id + (card_number[:6] + card_number[-4:])[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 7

Used for GET_TRANS_STATUS_BY_ORDER.

hash is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD.order_id.strrev(substr(card_number,0,6).substr(card_number,-4))))

This is Formula 2 with order_id in place of trans_id. On a transaction with no payer email - a CREDIT2CARD payout, for example - drop the strrev(email) term, as in Formula 6. If Payment Platform holds no card data for the order, see no card data on file.

Worked example
ParameterExample value
email[email protected]
passwordm3rch4ntP4ss
order_idorder-1234
card_number4111111111111111

The string that gets hashed:

MOC.ELPMAXE@NHOJM3RCH4NTP4SSORDER-12341111111114

The resulting hash:

98fe9590aca61eb784a59810990277dc

PHP

<?php
$email = "[email protected]";
$password = "m3rch4ntP4ss";
$order_id = "order-1234";
$card_number = "4111111111111111";

$raw = strtoupper(strrev($email) . $password . $order_id . strrev(substr($card_number,0,6) . substr($card_number,-4)));
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const email = "[email protected]";
const password = "m3rch4ntP4ss";
const order_id = "order-1234";
const card_number = "4111111111111111";

const raw = (rev(email) + password + order_id + rev(card_number.slice(0,6) + card_number.slice(-4))).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

email = "[email protected]"
password = "m3rch4ntP4ss"
order_id = "order-1234"
card_number = "4111111111111111"

raw = (email[::-1] + password + order_id + (card_number[:6] + card_number[-4:])[::-1]).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Formula 8

Used for SALE, SALE with auth=Y and DEBIT when the card is presented as a digital wallet token, that is when you send payment_token instead of card_number or card_token.

hash is calculated by the formula:

md5(strtoupper(strrev(email).PASSWORD))

This is Formula 1 with no card term, because there is no PAN and no card token in the request to build one from. See Appendix C for the digital wallet parameters.

Worked example
ParameterExample value
email[email protected]
passwordm3rch4ntP4ss

The string that gets hashed:

MOC.ELPMAXE@NHOJM3RCH4NTP4SS

The resulting hash:

1c40b8905bd426df293be7e91a8de181

PHP

<?php
$email = "[email protected]";
$password = "m3rch4ntP4ss";

$raw = strtoupper(strrev($email) . $password);
$hash = md5($raw);
echo $hash;

JavaScript (Node.js)

const crypto = require("crypto");
const rev = s => s.split("").reverse().join("");

const email = "[email protected]";
const password = "m3rch4ntP4ss";

const raw = (rev(email) + password).toUpperCase();
const hash = crypto.createHash("md5").update(raw).digest("hex");
console.log(hash);

Python

import hashlib

email = "[email protected]"
password = "m3rch4ntP4ss"

raw = (email[::-1] + password).upper()
hash_value = hashlib.md5(raw.encode()).hexdigest()
print(hash_value)

Transactions with no card data on file

CAPTURE, VOID, RETRY, GET_TRANS_STATUS, GET_TRANS_DETAILS and GET_TRANS_STATUS_BY_ORDER are signed with the card fragment that Payment Platform stored for the transaction. If Payment Platform holds no card fragment for that transaction, the card term cannot be built and a shorter input is used instead:

// CAPTURE, VOID, RETRY, GET_TRANS_STATUS, GET_TRANS_DETAILS
md5(strtoupper(strrev(trans_id)).PASSWORD)

// GET_TRANS_STATUS_BY_ORDER
md5(strtoupper(strrev(order_id)).PASSWORD)

CREDITVOID is the exception and does not switch to this shorter input: it keeps the Formula 2 shape and simply omits the card term.

This is not Formula 2 with empty terms. Three things differ: there is no email term, the identifier itself is reversed, and only the reversed identifier is uppercased - PASSWORD is appended as it is.

This applies to a digital wallet payment that stayed a wallet payment, that is Apple Pay or Google Pay where the wallet token was not decrypted into card data. When the wallet token is decrypted on our side, the card is stored, its fragment comes back to you in the card parameter of the response and the callback, and Formula 2 applies as usual. If you are not sure which case applies to your account, check whether card is populated in the response and callback of the initiating payment.

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)

See also

For related S2S Card documentation, see the following pages: