Skip to main content

Hash signature

Hash calculation

All hashes follow the same formula:

hash = sha1( md5( uppercase( concat_of_fields_in_required_order ) ) )

The list of fields differs per operation. Order matters: concatenate, do not delimit.

SHA256 hash mode

By default the inner digest is MD5, as shown above. If the Use SHA256 encryption algorithm for hash option is enabled for your protocol mapping (admin panel → Configuration → Protocol Mapping, per protocol / merchant), the inner digest is SHA256 instead: your request hash is validated with SHA256 and the callbacks you receive are signed with SHA256.

// default (MD5):
hash = sha1( md5( uppercase( concat_of_fields_in_required_order ) ) )

// "Use SHA256 encryption algorithm for hash" enabled:
hash = sha1( sha256( uppercase( concat_of_fields_in_required_order ) ) )

The field order is identical in both modes — only the inner hash function changes. Contact your administrator or account manager to confirm which mode is configured for your account.

warning

Compute hashes on your server, never in the browser. The merchant password is the secret that authenticates your requests; exposing it in client-side code is a complete compromise.

Authentication signature

FieldSource
1order.number
2order.amount
3order.currency
4order.description
5password

Capture / Refund signature

FieldSource
1payment_id
2amount
3password

Void / Retry / Get-status-by-payment_id signature

FieldSource
1payment_id
2password

Get-status-by-order_id signature

FieldSource
1order_id
2password

Recurring signature

FieldSource
1recurring_init_trans_id
2recurring_token
3order.number
4order.amount
5order.description
6password

Callback signature (verification on your side)

FieldSource
1id (= payment_public_id)
2order_number
3order_amount
4order_currency
5order_description
6password

Customer-return signature (in success_url / cancel_url query parameters)

Same fields as callback signature, in the same order.

Reference implementations

Node.js
const crypto = require("crypto");
const md5 = s => crypto.createHash("md5").update(s).digest("hex");
const sha1 = s => crypto.createHash("sha1").update(s).digest("hex");

function makeHash(fields, password) {
const raw = (fields.join("") + password).toUpperCase();
return sha1(md5(raw));
}

// Auth
makeHash([order.number, order.amount, order.currency, order.description], password);

// Capture
makeHash([payment_id, amount], password);

// Verify callback (constant-time comparison)
function verifyCallback(body, password) {
const expected = makeHash(
[body.id, body.order_number, body.order_amount, body.order_currency, body.order_description],
password
);
return crypto.timingSafeEqual(Buffer.from(body.hash), Buffer.from(expected));
}
Python
import hashlib, hmac

def make_hash(fields: list[str], password: str) -> str:
raw = ("".join(fields) + password).upper()
return hashlib.sha1(hashlib.md5(raw.encode()).hexdigest().encode()).hexdigest()

# Auth
make_hash([order["number"], order["amount"], order["currency"], order["description"]], password)

# Verify callback (constant-time)
def verify_callback(body: dict, password: str) -> bool:
expected = make_hash(
[body["id"], body["order_number"], body["order_amount"], body["order_currency"], body["order_description"]],
password
)
return hmac.compare_digest(body["hash"], expected)
PHP
function make_hash(array $fields, string $password): string {
$raw = strtoupper(implode("", $fields) . $password);
return sha1(md5($raw));
}

// Auth
make_hash([$order["number"], $order["amount"], $order["currency"], $order["description"]], $password);

// Verify callback (constant-time)
function verify_callback(array $body, string $password): bool {
$expected = make_hash(
[$body["id"], $body["order_number"], $body["order_amount"], $body["order_currency"], $body["order_description"]],
$password
);
return hash_equals($body["hash"], $expected);
}

[!NOTE] About SHA-1 + MD5. This signature scheme is kept for backward compatibility with the existing merchant base. The merchant password never leaves your server in plaintext; it is only mixed into the hash input. Always use constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest, hash_equals) when verifying callback signatures to prevent timing attacks.