Подпись оповещений (HMAC)
В этом разделе описан алгоритм подписи и проверки оповещений (callback-сообщений), использующий HMAC-ключ.
Для подписи используется симметричный алгоритм HMAC-SHA512 и общий секретный ключ, известный только вашей системе и HighHelp.
Назначение подписи
Подпись оповещений с HMAC решает следующие задачи:
-
подтверждает, что сообщение отправлено платформой HighHelp, владеющей секретным ключом;
-
гарантирует, что данные в теле оповещения не были изменены по пути.
| По умолчанию для подписи оповещений используется асимметричный алгоритм RSA-SHA256 (см. раздел Подпись оповещений (RSA)). HMAC-SHA512 доступен в качестве альтернативного метода. |
Смена алгоритма подписи с RSA на HMAC
Переключение RSA на HMAC применяется только к подписи оповещений.
Для подписи запросов можно использовать RSA-ключ или HMAC-ключ из набора API-ключей. Алгоритм выбирается заголовком x-access-merchant-algorithm.
|
Перед переключением подготовьте обработчик оповещений к проверке HMAC-подписи. |
Для переключения алгоритма подписи оповещений обратитесь к вашему менеджеру HighHelp. Смена выполняется на стороне HighHelp после согласования.
После переключения:
-
в окне API → Настройки Callback внизу отображается строка Текущий алгоритм: HMAC;
-
в окне отображается только блок HMAC key; блок Public Key не отображается.
Получение HMAC-ключа
Для проверки подписи оповещений используйте HMAC-ключ для подписи оповещений из окна API → Настройки Callback.
Ключ выгружается в файл hmac_callback_key_<id>.txt и доступен для скачивания только в момент генерации или обновления.
Для подписи запросов к API используется отдельный HMAC-ключ (см. Аутентификация и подпись запросов (HMAC)).
Генерация и обновление HMAC-ключа
|
При создании ключей кассы формируются HMAC-ключ и пара RSA-ключей. |
Если требуется сгенерировать HMAC-ключ для подписи оповещений отдельно или обновить существующий ключ, используйте инструкции ниже.
|
HMAC-ключ для подписи оповещений доступен для скачивания только в момент генерации или обновления. После скачивания в разделе API → Настройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа. |
|
Для касс, созданных до релиза 2.4.0, HMAC-ключ для подписи оповещений может отсутствовать. Воспользуйтесь инструкцией по генерации HMAC-ключа для подписи оповещений. |
Генерация HMAC-ключа
Если для кассы не был сгенерирован HMAC-ключ для подписи оповещений при первоначальной настройке, выполните следующие действия:
-
Откройте личный кабинет мерчанта.
-
Перейдите в раздел API → Настройки Callback.
-
Нажмите на иконку + в блоке HMAC key.
-
Сохраните ключ в защищенном хранилище и передайте команде разработки.
Обновление HMAC-ключа
Если требуется обновить HMAC-ключ для подписи оповещений, выполните следующие действия:
-
Откройте личный кабинет мерчанта.
-
Перейдите в раздел API → Настройки Callback.
-
Нажмите на иконку обновления в блоке HMAC key.
-
В открывшемся окне подтвердите операцию.
-
Нажмите и удерживайте кнопку подтверждения до завершения.
-
Скачайте файл
hmac_callback_key_<id>.txt. -
Обновите конфигурацию вашей интеграции, заменив старый HMAC-ключ для подписи оповещений на новый.
|
Обновление HMAC-ключа не переключает алгоритм подписи оповещений. |
|
После обновления HMAC-ключа старый ключ перестанет работать немедленно. Убедитесь, что новый ключ корректно настроен в вашей интеграции до начала использования. Рекомендуется выполнять обновление в период минимальной нагрузки на систему. |
Нормализация тела оповещения
Для формирования подписи используется нормализованное представление JSON-тела оповещения.
Алгоритм нормализации:
-
Обойдите JSON-структуру рекурсивно.
-
Для каждого значения сформируйте путь в виде
ключ1:ключ2:…:значение. -
Для массивов используйте индексы элементов:
:0,:1, … -
Для булевых значений используйте:
true→1,false→0. -
Отсортируйте все строки по алфавиту.
-
Соедините строки через
;.
Пример исходных данных:
{
"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
Пример реализации нормализации (Python3)
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)
if obj is None:
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)
Требования к нормализации
При реализации алгоритма нормализации учитывайте следующие требования:
-
Булевы значения: преобразуются в целочисленное представление (
true→1,false→0). -
Значения null: преобразуются в пустую строку
"". -
Числа: используйте стандартное строковое представление без локализации (разделителей групп разрядов, локальных форматов). Не добавляйте незначащие нули.
-
Массивы: порядок элементов сохраняется в исходной последовательности. Индексы элементов добавляются к пути как
:0,:1,:2, … -
Объекты: после формирования всех пар
путь:значениевыполняется сортировка по алфавиту по полной строке. -
Кодировка символов: используйте UTF-8 для кодирования перед применением Base64Url. Не изменяйте регистр символов.
-
Base64Url: кодируйте строку в формате Base64Url (RFC 4648): замените
+на-,/на_; символы выравнивания=не удаляйте. -
Пробелы и форматирование: не добавляйте и не удаляйте пробелы в значениях. Используйте точные значения из JSON-структуры.
Алгоритм формирования подписи
Подпись формируется на стороне HighHelp по следующему алгоритму:
-
Нормализация JSON-тела оповещения функцией
normalize_message. -
Кодирование нормализованной строки в Base64Url.
-
Конкатенация полученной строки и
timestamp(строка). -
Вычисление HMAC-SHA512 от UTF-8 байт результирующей строки с использованием секретного ключа.
-
Кодирование значения HMAC в Base64Url.
-
Передача подписи и метки времени в HTTP-заголовках оповещения.
В вашей интеграции воспроизведите шаги 1—5 и сравните вычисленную подпись с полученной.
Проверка подписи на стороне мерчанта
|
Для проверки подписи обработчик оповещений выполняет следующие проверки:
Коды ответа примера:
|
Для проверки подписи:
-
Получите JSON-тело оповещения и значения заголовков с:
-
маской HMAC-ключа (
x-access-token); -
меткой времени (
x-access-timestamp); -
подписью (
x-access-signature); -
идентификатором кассы (
x-access-merchant-id) для выбора корректного HMAC-ключа.
-
-
Найдите соответствующий секретный ключ для кассы.
-
Сравните маску из заголовка
x-access-tokenс маской вашего HMAC-ключа. -
Нормализуйте тело оповещения в строку
normalizedпо описанному алгоритму. -
Закодируйте
normalizedв Base64Url. -
Сконструируйте строку
message = encoded + timestamp. -
Вычислите HMAC-SHA512 от UTF-8 байт
messageс использованием секретного ключа. -
Декодируйте подпись
x-access-signatureиз Base64Url в байты и сравните байты подписи с вычисленным значением.
Пример проверки подписи (Python3)
import base64
import binascii
import hmac
import hashlib
def masked_hmac_key(key: str) -> str:
if not key or len(key) <= 6:
return "*******"
return key[:3] + "*******" + key[-3:]
def base64url_decode(data: str) -> bytes:
normalized = str(data).strip().replace("-", "+").replace("_", "/")
if not normalized:
raise ValueError("Invalid base64url string")
padding = "=" * (-len(normalized) % 4)
try:
return base64.b64decode(f"{normalized}{padding}", validate=True)
except binascii.Error as exc:
raise ValueError("Invalid base64url string") from exc
def verify_hmac_callback_signature(
payload: dict,
signature_b64url: str,
access_token: str,
secret_key: str,
timestamp: str,
) -> bool:
"""
Проверить подпись оповещения по алгоритму HMAC-SHA512.
"""
# Проверка маски ключа (x-access-token)
expected_mask = masked_hmac_key(secret_key)
if not hmac.compare_digest(expected_mask, access_token):
raise ValueError("Invalid x-access-token mask")
# Нормализация и подготовка сообщения
normalized = normalize_message(payload)
encoded = base64.urlsafe_b64encode(normalized.encode("utf-8")).decode("utf-8")
message = f"{encoded}{timestamp}".encode("utf-8")
# Вычисление ожидаемой подписи
expected_signature = hmac.new(secret_key.encode("utf-8"), message, hashlib.sha512).digest()
received_signature = base64url_decode(signature_b64url)
# Сравнение подписи, устойчивое к тайминговым атакам
return hmac.compare_digest(expected_signature, received_signature)
Рекомендации по безопасности
-
Храните секретный HMAC-ключ на стороне сервера.
-
Обновляйте ключи по запросу через специалиста HighHelp.
-
Не передавайте ключи по незащищенным каналам.
-
Не логируйте ключ полностью. Маскируйте значение: первые 3 символа + 7 астерисков (
*) + последние 3 символа.Пример маскирования ключа
def masked_hmac_key(key: str) -> str: if not key or len(key) <= 6: return "*******" return key[:3] + "*******" + key[-3:] -
Проверяйте идемпотентность оповещений по полям
project_id,payment_id,status,sub_status.Проверка индемпотентности гаррантирует, что повторная обработка одного и того же оповещения не изменяет конечный результат. Сохраняйте комбинацию этих полей для предотвращения дублирования операций. -
Проверяйте допустимое окно времени для
timestamp.
Форма для проверки подписи
Используйте форму ниже для проверки корректности формирования подписи HMAC-SHA512 для оповещений HighHelp.
-
Для проверки подписи оповещений используйте HMAC-ключ для подписи оповещений из API → Настройки Callback (
hmac_callback_key_<id>.txt).
|
Обработка введенных данных выполняется локально в браузере; данные не передаются на сервер. |
Выполнение проверки
-
Вставьте JSON-тело в первое поле.
-
Введите секретный ключ.
-
Укажите временную метку в формате Unix timestamp.
-
Вставьте подпись, которую необходимо проверить.
-
Нажмите кнопку Проверить подпись.
Форма отобразит пошаговый процесс формирования подписи и результат проверки предоставленной подписи.
Пример тестовых данных
Для проверки подписи можно использовать следующие тестовые данные:
JSON-тело:
{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}
Secret key: test-secret-key-123
Timestamp: 1716299720
Полученная подпись: signature-to-verify
После нажатия на кнопку Проверить подпись форма отобразит:
-
Нормализованное представление данных.
-
base64url(normalized). -
message = base64url(normalized) + timestamp. -
Вычисленную подпись (
x-access-signature). -
Результат сравнения подписи.