Перейти к содержанию

Формирование подписи для выплат

Раздел описывает формирование X-Signature для проведения выплат:

Для обоих способов выплаты и обоих этапов используется один алгоритм: RSA/SHA-256. Подпись передаётся в заголовке HTTP-запроса X-Signature.

Ключ для ЭЦП

  1. Сгенерируйте приватный ключ:

    openssl genrsa -out privatekey.pem 4096
    
  2. Получите публичную часть ключа:

    openssl rsa -in privatekey.pem -pubout -out publickey.pem
    
  3. Передайте публичную часть ключа в техническую поддержку KVELL для загрузки в систему. Приватный ключ должен оставаться на стороне мерчанта.

Внимание

Подпись проверяется по фактическому телу запроса. Подписывайте ровно тот JSON, который отправляете в API: не меняйте порядок полей и не сериализуйте данные повторно после формирования подписи. Сортировать параметры не нужно.

Алгоритм формирования подписи

  1. Получите точное JSON-тело запроса как строку в кодировке UTF-8.
  2. Добавьте secret_key магазина в конец строки без разделителя, пробела или переноса строки.
  3. Подпишите полученные байты приватным RSA-ключом, используя SHA-256.
  4. Закодируйте подпись в 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 в конец без пробела или переноса строки:

{"recipient_pan":"4111111111111111","amount":15000,"transaction":"payout-card-20260810-0001","description":"Выплата по договору 42","customer":"customer@example.com"}<secret-key>
{"phone":"79991234567","fio":"Иванов Иван Иванович","bank_id":"100000000008","amount":15000,"transaction":"payout-sbp-20260810-0001","description":"Выплата по договору 42"}<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, последовательно проверьте:

  1. В подпись переданы точные байты отправленного JSON без повторной сериализации.
  2. secret_key добавлен в конец тела без разделителя и не отправлен в самом JSON.
  3. Используется приватный ключ, соответствующий публичному ключу, переданному KVELL.
  4. Выбраны SHA-256 и PKCS#1 v1.5, а результат закодирован в Base64 без дополнительных переносов строк.
  5. В X-Signature передаётся подпись именно текущего запроса, включая его фактический набор и порядок полей.