Авторизация
API-ключи sk_test / sk_live, формат запросов, HMAC-подпись и идемпотентность. Bearer-ключ обязателен, подпись — опционально, но настоятельно.
Все защищённые запросы требуют заголовок Authorization: Bearer <api_key>. Ключи выпускаются на магазин в разделе Интеграция.
sk_test_…- тестовый ключ. Ходит в симулятор, реальный банк не вызывается. Доступен всегда.
sk_live_…- боевой ключ. Вернёт 403, если live-режим у мерчанта ещё не активирован.
Формат запроса
Тела запросов и ответов — JSON в UTF-8. Дата и время в формате RFC 3339, в UTC. Денежные суммы целые, в минорных единицах валюты: копейки для RUB, центы для USD. Идентификаторы — UUIDv4.
curl -X POST https://api.balancedpay.pro/api/v1/public/payments \ -H 'Authorization: Bearer sk_test_x9k…' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: 7f2a-pay-1042'
Настройки в ЛК и HMAC-подпись
Всё, что ниже, делается в личном кабинете: /cabinet/integration → карточка магазина. Через публичное API эти параметры менять нельзя: они для человека, не для сервера.
На уровне магазина
- API-ключи.
sk_test_…ходит в симулятор,sk_live_…в боевой банк. На магазин можно выпустить несколько ключей, любой отзывается одной кнопкой. Полный секрет показывается ровно один раз — сохраните сразу. - Allow-list IP. Список IPv4, IPv6, CIDR, с которых разрешены запросы по ключам этого магазина. Запрос с другого адреса отклонится с
403 ip_not_allowed. Пустой список — ограничения нет. - HMAC-секрет магазина (
thm_…). Используется, чтобы подписывать ваши запросы к нашему API. Если на магазине включена опция «Требовать подпись», запросы без заголовкаX-Freefin-Signature: sha256=<hex>вернут 401. Это второй фактор поверх Bearer-ключа: даже если ключ протёк через лог или прокси, без секрета подделать запрос нельзя.
Подпись запроса (опционально)
Когда на магазине включена опция «Требовать подпись», каждый запрос к API нужно подписать секретом магазина (thm_…) и положить в заголовок X-Freefin-Signature: sha256=<hex>. Алгоритм: HMAC-SHA256 от raw body запроса, то есть тех самых байт, что уйдут в сеть, до любых преобразований. Для GET с пустым телом подписывается пустая строка.
import crypto from 'node:crypto'
const body = JSON.stringify({
amount: 150000, currency: 'RUB', method: 'sbp', order_id: 'ORDER-1042',
})
const sig = 'sha256=' + crypto
.createHmac('sha256', process.env.FREEFIN_TERMINAL_SECRET) // thm_…
.update(body)
.digest('hex')
await fetch('https://api.balancedpay.pro/api/v1/public/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FREEFIN_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '7f2a-pay-1042',
'X-Freefin-Signature': sig,
},
body, // ВАЖНО: ровно тот же body, что подписали — JSON.stringify один раз
})Webhook endpoints
На магазин можно завести несколько endpoint'ов. У каждого свой URL, свой набор событий и свой секрет (whs_…). Этим секретом мы подписываем webhook'и, которые шлём вам. Подробнее в разделе Webhooks.
thm_…) подписывает запросы от вас к нам. Секрет webhook-endpoint'а (whs_…) подписывает webhook'и от нас к вам. Они независимы и ротируются отдельно.Идемпотентность
Для POST, создающих ресурс (платёж, выплата), передавайте заголовок Idempotency-Key до 64 символов. При повторном запросе с тем же ключом мы вернём ранее созданный объект и не создадим дубль. Ключ уникален в пределах одного мерчанта; используйте свой order_id или UUID на стороне приложения.
Повтор с тем же ключом и тем же телом — 200 OK с уже созданным объектом и полем idempotent: true. Повтор с тем же ключом, но другим телом — 409 idempotent_conflict.