Формирование подписи для выплат
Раздел описывает формирование X-Signature для проведения выплат:
Для обоих способов выплаты и обоих этапов используется один алгоритм: RSA/SHA-256.
Подпись передаётся в заголовке HTTP-запроса X-Signature.
Ключ для ЭЦП
-
Сгенерируйте приватный ключ:
-
Получите публичную часть ключа:
-
Передайте публичную часть ключа в техническую поддержку KVELL для загрузки в систему. Приватный ключ должен оставаться на стороне мерчанта.
Внимание
Подпись проверяется по фактическому телу запроса. Подписывайте ровно тот JSON, который отправляете в API: не меняйте порядок полей и не сериализуйте данные повторно после формирования подписи. Сортировать параметры не нужно.
Алгоритм формирования подписи
- Получите точное JSON-тело запроса как строку в кодировке UTF-8.
- Добавьте
secret_keyмагазина в конец строки без разделителя, пробела или переноса строки. - Подпишите полученные байты приватным RSA-ключом, используя SHA-256.
- Закодируйте подпись в Base64 и передайте строку в заголовке
X-Signature.
Примеры кода
В каждом примере тело сериализуется один раз: одна и та же строка используется для подписи и отправки в API.
Python
Сначала определите общую функцию формирования подписи и отправки запроса:
import base64
import json
import requests
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
x_api_key = "<api-key>"
secret_key = "<secret-key>"
def send_payout(url: str, payload: dict):
data = json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
)
with open("privatekey.pem", "rb") as private_key_file:
key = RSA.importKey(private_key_file.read())
digest = SHA256.new(f"{data}{secret_key}".encode("utf-8"))
signature = PKCS1_v1_5.new(key).sign(digest)
x_signature = base64.b64encode(signature).decode("utf-8")
return requests.post(
url,
data=data.encode("utf-8"),
headers={
"Content-Type": "application/json; charset=UTF-8",
"X-Api-Key": x_api_key,
"X-Signature": x_signature,
},
)
Затем выберите тип выплаты:
payload = {
"recipient_pan": "4111111111111111",
"amount": 15000,
"transaction": "payout-card-20260810-0001",
"description": "Выплата по договору 42",
"customer": "customer@example.com",
}
response = send_payout(
"https://api.pay.kvell.group/v1/orders/account2card",
payload,
)
print(response.status_code)
print(response.text)
payload = {
"phone": "79991234567",
"fio": "Иванов Иван Иванович",
"bank_id": "100000000008",
"amount": 15000,
"transaction": "payout-sbp-20260810-0001",
"description": "Выплата по договору 42",
}
response = send_payout(
"https://api.pay.kvell.group/v1/orders/payout/sbp",
payload,
)
print(response.status_code)
print(response.text)
PHP
Сначала определите общую функцию формирования подписи:
<?php
function buildSignedRequest(array $payload, string $secretKey): array
{
$data = json_encode(
$payload,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
if ($data === false) {
throw new RuntimeException('Не удалось сериализовать тело запроса');
}
$key = openssl_pkey_get_private(
file_get_contents('privatekey.pem')
);
$signature = '';
openssl_sign(
$data . $secretKey,
$signature,
$key,
'sha256WithRSAEncryption'
);
return [$data, base64_encode($signature)];
}
$secretKey = '<secret-key>';
Затем выберите тип выплаты:
<?php
$url = 'https://api.pay.kvell.group/v1/orders/account2card';
$payload = [
'recipient_pan' => '4111111111111111',
'amount' => 15000,
'transaction' => 'payout-card-20260810-0001',
'description' => 'Выплата по договору 42',
'customer' => 'customer@example.com',
];
[$data, $xSignature] = buildSignedRequest($payload, $secretKey);
print('URL: ' . $url . PHP_EOL);
print('X-Signature: ' . $xSignature . PHP_EOL);
print('Body: ' . $data . PHP_EOL);
<?php
$url = 'https://api.pay.kvell.group/v1/orders/payout/sbp';
$payload = [
'phone' => '79991234567',
'fio' => 'Иванов Иван Иванович',
'bank_id' => '100000000008',
'amount' => 15000,
'transaction' => 'payout-sbp-20260810-0001',
'description' => 'Выплата по договору 42',
];
[$data, $xSignature] = buildSignedRequest($payload, $secretKey);
print('URL: ' . $url . PHP_EOL);
print('X-Signature: ' . $xSignature . PHP_EOL);
print('Body: ' . $data . PHP_EOL);
OpenSSL
Сохраните в файл data_to_be_signed точное тело POST-запроса и добавьте secret_key в конец без пробела или
переноса строки:
Сформируйте подпись:
openssl dgst \
-sha256 \
-sign /path/to/privatekey.pem \
/path/to/data_to_be_signed \
| base64 \
| tr -d '\n'
Где:
/path/to/privatekey.pem— путь к приватному ключу;/path/to/data_to_be_signed— путь к файлу с телом запроса и добавленным в конецsecret_key.
Полученную Base64-строку передайте в заголовке X-Signature. В API отправьте только исходное JSON-тело — без
добавленного secret_key.
Ошибка подписи
Если API вернул ошибку 20002, последовательно проверьте:
- В подпись переданы точные байты отправленного JSON без повторной сериализации.
secret_keyдобавлен в конец тела без разделителя и не отправлен в самом JSON.- Используется приватный ключ, соответствующий публичному ключу, переданному KVELL.
- Выбраны SHA-256 и
PKCS#1 v1.5, а результат закодирован в Base64 без дополнительных переносов строк. - В
X-Signatureпередаётся подпись именно текущего запроса, включая его фактический набор и порядок полей.