Аутентификация и подпись запросов (HMAC)

В этом разделе описано, как выполнять аутентификацию запросов к HighHelp API с использованием алгоритма HMAC-SHA512 и как формировать цифровую подпись.

Регистрация в системе

Регистрацию выполняйте через личный кабинет мерчанта. Доступ в личный кабинет предоставляет специалист HighHelp.

При генерации ключей для кассы одновременно создаются:

  • пара RSA-ключей (публичный и приватный);

  • секретный HMAC-ключ.

Порядок действий:

  1. Создайте кассу во вкладке Кассы.

  2. Обратитесь к прикрепленному специалисту HighHelp для настройки кассы и проведения верификации.

  3. После настройки и верификации кассы перейдите во вкладку API.

  4. Найдите созданную кассу и нажмите кнопку Сгенерировать ключ.

  5. В открывшемся окне проверьте название кассы и URL.

  6. Нажмите и удерживайте кнопку Сгенерировать ключи.

  7. Скопируйте и сохраните:

    • UUID — идентификатор кассы. Используется при взаимодействии с API и передается в заголовке x-access-merchant-id;

    • Private key (RSA) — приватный RSA-ключ в формате PEM. Используется для формирования цифровой подписи запросов;

    • Public key (RSA) — публичный RSA-ключ. Используется для проверки подписи оповещений от HighHelp;

    • Private key (HMAC) — секретный HMAC-ключ. Используется для формирования подписи запросов и оповещений при настроенном алгоритме HMAC-SHA512.

      Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение HMAC-ключа; повторное скачивание недоступно, доступно только обновление ключа.

      Ключ выгружается в файл и не хранится на стороне HighHelp в открытом виде. На стороне HighHelp хранится только хеш и служебная информация, необходимая для проверки подписи.

  8. Передайте значения UUID и Private key (HMAC) команде разработки или DevOps-специалисту и обеспечьте их защищенное хранение.

Не храните секретный HMAC-ключ в открытом виде в репозиториях, логах и системах мониторинга. Используйте специализированные хранилища секретов и ограничивайте доступ к нему по принципу наименьших привилегий.

Генерация и обновление HMAC-ключа

При генерации ключей для кассы одновременно создаются HMAC-ключ и пара RSA-ключей. Если требуется сгенерировать HMAC-ключ отдельно или обновить существующий ключ, используйте инструкции ниже.

Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа.

Генерация HMAC-ключа

Если для кассы не был сгенерирован HMAC-ключ при первоначальной настройке, выполните следующие действия:

  1. Откройте личный кабинет мерчанта.

  2. Перейдите в раздел APIНастройки Callback.

  3. Нажмите на иконку + в блоке HMAC key.

  4. Сохраните ключ в защищенном хранилище и передайте команде разработки.

Обновление HMAC-ключа

Если для кассы настроен алгоритм HMAC-SHA512 и требуется обновить только HMAC-ключ без обновления RSA-ключей, выполните следующие действия:

  1. Откройте личный кабинет мерчанта.

  2. Перейдите в раздел APIНастройки Callback.

  3. Убедитесь, что текущий алгоритм подписи — HMAC (отображается внизу модального окна: Алгоритм: HMAC).

  4. Нажмите на кнопку Обновить ключ в блоке HMAC key.

  5. В открывшемся окне подтвердите операцию.

  6. Сохраните новый секретный HMAC-ключ.

  7. Обновите конфигурацию интеграции на стороне мерчанта, заменив старый HMAC-ключ на новый.

После обновления HMAC-ключа старый ключ перестанет работать немедленно. Убедитесь, что новый ключ корректно настроен на стороне мерчанта до начала использования. Рекомендуется выполнять обновление в период минимальной нагрузки на систему.

Получение HMAC-ключа

Для проверки подписи оповещений используйте секретный HMAC-ключ, настроенный для кассы. Тот же ключ используется для формирования подписи запросов к API.

Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа.

Описание формата подписи оповещений и алгоритма проверки приведено в разделе Подпись оповещений (HMAC).

Смена алгоритма подписи с RSA на HMAC

Для смены алгоритма подписи с RSA на HMAC обратитесь к специалисту HighHelp. Смена алгоритма выполняется на стороне HighHelp после согласования.

Перед переключением убедитесь, что:

  • HMAC-ключ для кассы сгенерирован;

  • интеграция на стороне мерчанта готова к работе с HMAC-подписью;

  • обновлены скрипты для формирования подписи запросов и проверки подписи оповещений.

После смены алгоритма:

  • в личном кабинете доступен только HMAC-ключ настроенного для кассы алгоритма подписи;

  • в настройках оповещений отображается текущий алгоритм — HMAC (в нижней части модального окна: Алгоритм: HMAC);

  • все запросы и оповещения должны использовать HMAC-SHA512.

Аутентификация в API

Для HMAC-аутентификации запросов используйте следующие HTTP-заголовки:

  • x-access-timestamp

  • x-access-merchant-id

  • x-access-signature

  • x-access-token

  • x-access-merchant-algorithm

Алгоритм подписи (схематично):

x-access-merchant-algorithm = "HMAC-SHA512"
message = base64url(normalized_payload) + str(timestamp)
x-access-signature = base64url(HMAC_SHA512(secret_key, message))

Где:

  • normalized_payload — нормализованное представление JSON-тела запроса;

  • timestamp — значение заголовка x-access-timestamp в виде строки;

  • secret_key — секретный HMAC-ключ кассы (байтовый массив).

Если тело запроса отсутствует, используйте пустой объект {} и нормализуйте его как обычный JSON.

Заголовок x-access-timestamp

x-access-timestamp содержит время формирования запроса в формате Unix timestamp (количество секунд с 01.01.1970 00:00:00 UTC), указанное строкой.

Пример:

x-access-timestamp: 1716299720

Используйте время сервера, синхронизированное по NTP. Не используйте локальное время клиента браузера или мобильного приложения.

Заголовок x-access-merchant-id

x-access-merchant-id содержит идентификатор кассы. Используйте значение UUID, полученное при генерации ключей для кассы.

В примерах кода идентификатор передается через переменную project_id.

Пример:

x-access-merchant-id: 57aff4db-b45d-42bf-bc5f-b7a499a01782

Заголовок x-access-merchant-algorithm

x-access-merchant-algorithm определяет алгоритм подписи, используемый для данного запроса.

Для HMAC используйте значение:

x-access-merchant-algorithm: HMAC-SHA512

Для HMAC-подписи всегда указывайте x-access-merchant-algorithm: HMAC-SHA512. При отсутствии заголовка или при неверном значении запрос будет отклонен.

Заголовок x-access-token

x-access-token содержит маску секретного HMAC-ключа. Маска используется для идентификации ключа без раскрытия полного секрета.

Маску сформируйте по правилу:

<первые 3 символа ключа> + 7 астерисков (*) + <последние 3 символа ключа>

Пример функции формирования маски:

def masked_hmac(hmac_key: str) -> str:
    return hmac_key[:3] + "*******" + hmac_key[-3:]

Пример значения заголовка:

x-access-token: abc*******xyz

Не передавайте полный секретный HMAC-ключ в заголовках, теле запроса или URL. Используйте только маску в x-access-token.

Заголовок x-access-signature

x-access-signature содержит цифровую подпись запроса. Подпись формируется по алгоритму HMAC-SHA512.

Формат (схематично):

message = base64url(normalized_payload) + str(timestamp)
signature_bytes = HMAC_SHA512(secret_key, message)
x-access-signature = base64url(signature_bytes)

Порядок формирования подписи:

  1. Нормализуйте тело запроса в строку normalized_payload с помощью алгоритма нормализации.

  2. Закодируйте строку normalized_payload в Base64Url, получите строку encoded_payload.

  3. Сконкатенируйте encoded_payload и timestamp (значение заголовка x-access-timestamp), получите строку message.

  4. Вычислите HMAC-SHA512 от message с использованием секретного HMAC-ключа.

  5. Закодируйте результат в Base64Url.

  6. Передайте полученное значение в заголовке x-access-signature.

Нормализация тела запроса

Для формирования подписи используется нормализованное представление JSON-тела запроса.

Алгоритм нормализации:

  1. Выполните рекурсивный обход JSON-структуры (объекты и массивы).

  2. Соберите пары в формате путь:значение, где путь строится через двоеточие:

    • для объектов: parent:ключ;

    • для массивов: parent:индекс.

  3. Преобразуйте булевы значения: true в 1, false в 0.

  4. Используйте стандартное строковое представление чисел без локалей и разделителей (только цифры и, при необходимости, знак).

  5. Отсортируйте все пары по алфавиту.

  6. Склейте пары в одну строку, разделяя их символом ;.

Пример исходного JSON:

{
  "amount": 100,
  "status": "success",
  "is_paid": true,
  "data": {
    "id": 123,
    "is_active": false
  }
}

Результат нормализации:

amount:100;data:id:123;data:is_active:0;is_paid:1;status:success

Пример функций нормализации на Python:

def parse_json(prefix, obj, result):
    """
    Рекурсивный обход JSON-структуры для формирования пар путь:значение.
    """
    if isinstance(obj, dict):
        for key, value in obj.items():
            if isinstance(key, bool):
                key = int(key)
            new_prefix = f"{prefix}:{key}" if prefix else str(key)
            parse_json(new_prefix, value, result)
    elif isinstance(obj, list):
        for index, item in enumerate(obj):
            if isinstance(item, bool):
                item = int(item)
            new_prefix = f"{prefix}:{index}"
            parse_json(new_prefix, item, result)
    else:
        if isinstance(obj, bool):
            obj = int(obj)
        result.append(f"{prefix}:{obj}")


def normalize_message(payload: dict) -> str:
    """
    Нормализация JSON в детерминированную строку (формат: путь:значение через ;).
    """
    items: list[str] = []
    parse_json("", payload, items)
    items.sort()
    return ";".join(items)

Если тело запроса отсутствует, используйте пустой объект {} (например, payload = {}) и нормализуйте его как обычный JSON.

Требования к нормализации

При реализации алгоритма нормализации учитывайте следующие требования:

  • Булевы значения: преобразуются в целочисленное представление (true1, false0).

  • Значения null: преобразуются в строку None. Не используйте пустые строки или пробелы.

  • Числа: используйте стандартное строковое представление без локализации (разделителей групп разрядов, локальных форматов). Не добавляйте незначащие нули.

  • Массивы: порядок элементов сохраняется в исходной последовательности. Индексы элементов добавляются к пути как :0, :1, :2, …​

  • Объекты: после формирования всех пар путь:значение выполняется сортировка по алфавиту по полной строке.

  • Кодировка символов: используйте UTF-8 для кодирования перед применением Base64Url. Не изменяйте регистр символов.

  • Пробелы и форматирование: не добавляйте и не удаляйте пробелы в значениях. Используйте точные значения из JSON-структуры.

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

  1. Сформируйте объект payload с телом запроса.

  2. Нормализуйте payload функцией normalize_message():

    joined_result = normalize_message(payload)
  3. Закодируйте нормализованную строку в Base64Url и добавьте метку времени:

    timestamp = int(time.time())
    message = "{}{}".format(
        base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"),
        str(timestamp),
    )
  4. Вычислите HMAC-SHA512 от строки message с использованием секретного HMAC-ключа.

  5. Закодируйте результат в Base64Url.

  6. Передайте полученное значение в заголовке x-access-signature.

Пример запроса с подписью (Python3)

Ниже приведен пример формирования подписи HMAC-SHA512 и отправки запроса к API.

import base64
import json
import time
import hmac
import hashlib
import requests

url = "https://api.hh-processing.com/api/v1/payment/p2p/payin"

# Идентификатор кассы (UUID)
project_id = "57aff4db-b45d-42bf-bc5f-b7a499a01782"

# Секретный HMAC-ключ (байты)
secret_key = b"<YOUR-HMAC-SECRET-KEY-BYTES>"

payload = {
    "general": {
        "project_id": project_id
    }
}


def parse_json(prefix, obj, result):
    if isinstance(obj, dict):
        for key, value in obj.items():
            if isinstance(key, bool):
                key = int(key)
            new_prefix = f"{prefix}:{key}" if prefix else str(key)
            parse_json(new_prefix, value, result)
    elif isinstance(obj, list):
        for index, item in enumerate(obj):
            if isinstance(item, bool):
                item = int(item)
            new_prefix = f"{prefix}:{index}"
            parse_json(new_prefix, item, result)
    else:
        if isinstance(obj, bool):
            obj = int(obj)
        result.append(f"{prefix}:{obj}")


def normalize_message(data: dict) -> str:
    items = []
    parse_json("", data, items)
    items.sort()
    return ";".join(items)


def masked_hmac(hmac_key: str) -> str:
    """
    Маскирование HMAC-ключа для безопасного логирования.
    """
    return hmac_key[:3] + "*******" + hmac_key[-3:]


# Нормализация тела запроса
normalized = normalize_message(payload)

# Кодирование в Base64Url
encoded = base64.urlsafe_b64encode(normalized.encode("utf-8")).decode("utf-8")

# Метка времени Unix (секунды)
timestamp = int(time.time())

# Формирование сообщения для подписи
message = f"{encoded}{timestamp}".encode("utf-8")

# Вычисление HMAC-SHA512
signature_bytes = hmac.new(secret_key, message, hashlib.sha512).digest()
signature_b64url = base64.urlsafe_b64encode(signature_bytes).decode("utf-8")

# Маска HMAC-ключа для x-access-token
hmac_mask = masked_hmac(secret_key.decode("utf-8"))

# Формирование заголовков
headers = {
    "content-type": "application/json",
    "x-access-merchant-id": project_id,
    "x-access-timestamp": str(timestamp),
    "x-access-signature": signature_b64url,
    "x-access-merchant-algorithm": "HMAC-SHA512",
    "x-access-token": hmac_mask,
}

# Сериализация тела запроса (JSON)
dumped = json.dumps(payload, separators=(",", ":")) if payload else "{}"

response = requests.post(url, headers=headers, data=dumped)
print(response.status_code)

Рекомендации по безопасности

  • Храните секретный HMAC-ключ на стороне сервера.

  • Обновляйте ключи по запросу через специалиста HighHelp.

  • Не передавайте ключи по незащищенным каналам.

  • Не логируйте ключ полностью. Маскируйте значение: первые 3 символа + 7 астерисков (*) + последние 3 символа.

    Пример маскирования ключа
    def masked_hmac_key(key: str) -> str:
        return key[:3] + "*******" + key[-3:]

Форма для проверки подписи

Используйте форму ниже для проверки корректности формирования подписи HMAC-SHA512 для запросов к API HighHelp.

Обработка введенных данных выполняется локально в браузере; данные не передаются на сервер.

Выполнение проверки

  1. Вставьте JSON-тело запроса в первое поле.

  2. Введите ваш секретный ключ.

  3. Укажите временную метку в формате Unix timestamp.

  4. Вставьте подпись, которую необходимо проверить.

  5. Нажмите кнопку Проверить подпись.

Форма отобразит пошаговый процесс формирования подписи и результат проверки предоставленной подписи.

Пример тестовых данных

Для проверки подписи можно использовать следующие тестовые данные:

JSON-тело запроса:

{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}

Секретный ключ: test-secret-key

Timestamp: 1716299720

После нажатия на кнопку Проверить подпись форма отобразит:

  1. Нормализованное представление данных.

  2. Используемую временную метку.

  3. Вычисленную подпись.

  4. Результат проверки предоставленной подписи.