🔐 Безопасность
Аутентификация
Для обеспечения безопасности все запросы должны быть подписаны с использованием алгоритма HMAC-SHA512.
Подпись генерируется с использованием вашего приватного ключа, который предоставляется в Личном Кабинете. Публичный ключ и NONCE передаются в заголовках.
Формула подписи
Сообщение для подписи формируется одинаково для всех типов запросов:
path+body+nonce
pathПуть к эндпоинту, например:
/api/v1/balancebodyJSON строка тела запроса с ключами в алфавитном порядке (пустая строка для GET)
nonceУникальное числовое значение — всегда в конце
Важно: JSON ключи в теле запроса должны идти в алфавитном порядке! Значение
nonce ВСЕГДА добавляется в конец сообщения для подписи.GET запрос
/api/v1/balance1721585422POST запрос
/api/v1/pay-in{"amount":"1000","bankId":1,"callbackURL":"https://test.com/callback","currencyId":1,"externalID":"test123","method":"CARD"}1721585422Генерация подписи
const crypto = require('crypto');
function sortObjectKeys(obj) {
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
return obj;
}
const sortedObj = {};
const keys = Object.keys(obj).sort();
for (const key of keys) {
sortedObj[key] = sortObjectKeys(obj[key]);
}
return sortedObj;
}
function generateSignature(path, body, nonce, privateKey) {
let bodyString = '';
if (body && typeof body === 'object') {
const sortedBodyObj = sortObjectKeys(body);
bodyString = JSON.stringify(sortedBodyObj);
}
// Формируем строку: path + body + nonce
const stringToSign = path + bodyString + nonce;
const signature = crypto.createHmac('sha512', privateKey)
.update(stringToSign)
.digest('hex');
return { stringToSign, signature, body: bodyString };
}
// Пример для GET запроса
const result = generateSignature(
'/api/v1/balance',
null,
generateNonce(),
'your_private_key_here'
);
// Пример для POST запроса
const postResult = generateSignature(
'/api/v1/pay-in',
{
amount: "1000",
bankId: 1,
callbackURL: "https://test.com/callback",
currencyId: 1,
externalID: "test123",
method: "CARD"
},
generateNonce(),
'your_private_key_here'
);<?php
function sortObjectKeys($obj) {
if ($obj === null || !is_array($obj)) {
return $obj;
}
ksort($obj);
foreach ($obj as $key => $value) {
$obj[$key] = sortObjectKeys($value);
}
return $obj;
}
function generateSignature($path, $body, $nonce, $privateKey) {
$bodyString = '';
if ($body && is_array($body)) {
$sortedBodyObj = sortObjectKeys($body);
$bodyString = json_encode($sortedBodyObj, JSON_UNESCAPED_SLASHES);
}
// path + body + nonce
$stringToSign = $path . $bodyString . $nonce;
$signature = hash_hmac('sha512', $stringToSign, $privateKey);
return [
'stringToSign' => $stringToSign,
'signature' => $signature,
'body' => $bodyString
];
}
$result = generateSignature(
'/api/v1/balance',
null,
generateNonce(),
'your_private_key_here'
);
?>import hmac
import hashlib
import json
def sort_object_keys(obj):
if obj is None or not isinstance(obj, dict):
return obj
return {key: sort_object_keys(obj[key]) for key in sorted(obj.keys())}
def generate_signature(path, body, nonce, private_key):
body_string = ''
if body and isinstance(body, dict):
sorted_body = sort_object_keys(body)
body_string = json.dumps(sorted_body, separators=(',', ':'))
# path + body + nonce
string_to_sign = path + body_string + str(nonce)
signature = hmac.new(
private_key.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha512
).hexdigest()
return {
'stringToSign': string_to_sign,
'signature': signature,
'body': body_string
}using System;
using System.Security.Cryptography;
using System.Text;
// Канонизация JSON (сортировка ключей)
public static class JsonCanonicalizer
{
public static string Canonicalize(object body)
{
if (body == null) return string.Empty;
var token = JToken.FromObject(body);
return JsonConvert.SerializeObject(SortToken(token));
}
private static JToken SortToken(JToken token)
{
if (token is JObject obj)
{
var sortedObj = new JObject();
foreach (var prop in obj.Properties()
.OrderBy(p => p.Name, StringComparer.Ordinal))
sortedObj[prop.Name] = SortToken(prop.Value);
return sortedObj;
}
if (token is JArray arr)
{
var newArr = new JArray();
foreach (var item in arr) newArr.Add(SortToken(item));
return newArr;
}
return token;
}
}
// Генерация подписи
public static class PlatixSignature
{
public static string ComputeSignature(
string path, string bodyString,
string nonce, string privateKey)
{
string stringToSign = path + bodyString + nonce;
using var hmac = new HMACSHA512(
Encoding.UTF8.GetBytes(privateKey)
);
var hash = hmac.ComputeHash(
Encoding.UTF8.GetBytes(stringToSign)
);
return BitConverter.ToString(hash)
.Replace("-", "").ToLowerInvariant();
}
}Генерация NONCE
NONCE — уникальное числовое значение для каждого запроса. Система запоминает последний NONCE для каждого мерчанта и отклоняет запросы с меньшим или равным значением.
Структура NONCE:
TTTTTTTTTTTTT (13 цифр времени) + CCC (3 цифры счётчика) + RR (2 случайных цифры) = 18 цифрВремя (13 цифр)→Date.now() — текущее время в миллисекундах
Счётчик (3 цифры)→Инкрементируется при каждом вызове, сбрасывается на 999
Случайность (2 цифры)→Случайное число 0-99 для дополнительной энтропии
let counter = 0;
function generateNonce() {
const timePart = Date.now(); // 13 цифр
const counterPart = (counter++ % 1000).toString().padStart(3, "0"); // 3
const randomPart = Math.floor(Math.random() * 100)
.toString().padStart(2, "0"); // 2
return parseInt(`${timePart}${counterPart}${randomPart}`);
}
const nonce = generateNonce();
// Example: 172325680000000112<?php
function generateNonce() {
static $counter = 0;
$timePart = (string) round(microtime(true) * 1000); // 13 цифр
$counterPart = str_pad(($counter++ % 1000), 3, "0", STR_PAD_LEFT);
$randomPart = str_pad(mt_rand(0, 99), 2, "0", STR_PAD_LEFT);
return (int) ($timePart . $counterPart . $randomPart);
}
$nonce = generateNonce();
?>import time
import random
def generate_nonce():
counter = 0
def inner():
nonlocal counter
time_part = str(int(time.time() * 1000)) # 13 цифр
counter_part = str(counter % 1000).zfill(3) # 3 цифры
random_part = str(random.randint(0, 99)).zfill(2) # 2 цифры
counter += 1
return int(time_part + counter_part + random_part)
return inner()using System;
using System.Security.Cryptography;
using System.Threading;
public static class NonceGenerator
{
private static int _counter = 0;
public static string GenerateNonce()
{
string timePart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
string counterPart = (Interlocked.Increment(ref _counter) % 1000)
.ToString().PadLeft(3, '0');
string randomPart = RandomNumberGenerator.GetInt32(0, 100)
.ToString().PadLeft(2, '0');
return $"{timePart}{counterPart}{randomPart}";
}
public static long GenerateNonceAsLong() => long.Parse(GenerateNonce());
}Преимущества подхода: поддержка до 1,000 RPS без коллизий, 1,000,000 уникальных значений/мс, совместимость с MySQL, PostgreSQL, Redis.
Обязательные заголовки
Content-Typeapplication/json
Public-KeyВаш публичный ключ из Личного Кабинета
nonceУникальное числовое значение (должно быть больше предыдущего)
SignatureHMAC-SHA512 подпись, сгенерированная с частным ключом
http
Content-Type: application/json nonce: 1717025133 Public-Key: your_public_key_here Signature: 2816894fc8ebe05d47e96eca553ee3ca59863ae8d41a25a42d92b71df5e0e95b4490cfc8ff180e7575c5dbbc643ab3842ca05ae8bbb9f08e57c58cab748f8677