Один API — все способы оплаты. Подключение — через оператора: напишите нам,
после проверки проекта вы получите пару ключей — vnt_live_pub_… (публичный) и
vnt_live_sec_… (секрет подписи, по сети не передаётся). Сначала включается
песочница: полный цикл — платёж, статусы, вебхуки — без реальных денег;
боевой режим включаем после успешного теста интеграции.
Каждый запрос подписывается HMAC-SHA256 от канонической строки:
поля запроса (кроме signature и пустых) сортируются по имени ключа,
склеиваются как key=value через &.
const crypto = require('crypto')
function sign(params, secret) {
const s = Object.keys(params)
.filter(k => k !== 'signature' && params[k] !== undefined && params[k] !== null && params[k] !== '')
.sort()
.map(k => `${k}=${typeof params[k] === 'object' ? JSON.stringify(params[k]) : params[k]}`)
.join('&')
return crypto.createHmac('sha256', secret).update(s).digest('hex')
}
POST https://ventapay.click/api/v1/payment/create
Content-Type: application/json
{
"api_key": "vnt_live_pub_…",
"order_id": "order-123", // ОБЯЗАТЕЛЕН, до 64 символов; повтор вернёт тот же платёж
"amount": 299.00, // ₽, здесь — В РУБЛЯХ
"method": "sbp", // sbp | card | crypto
"description": "Подписка на месяц",
"customer": "tg:123456789", // опционально
"signature": "…" // подпись всех полей выше
}
Ответ: { payment_id, status, amount, payment_url, provider, is_test } —
покупателя редиректите на payment_url.
Почему order_id обязателен: без него один и тот же подписанный запрос можно
переслать повторно и получить сколько угодно платежей. С ним повтор возвращает
duplicate: true и тот же payment_id — это защита и от повторов, и от
двойного списания при ретраях вашей стороны.
Лимиты суммы: от 10 до 300000 ₽. Частота: до 60 запросов в минуту с адреса.
GET https://ventapay.click/api/v1/payment/{payment_id}?api_key=vnt_live_pub_…&signature=…
Подписываются параметры api_key и id (id — из пути).
Статусы: created → pending → paid | failed | expired, после возврата —
refunded.
POST https://ventapay.click/api/v1/payment/{payment_id}/refund
{
"api_key": "vnt_live_pub_…",
"amount": 100.00, // ₽; без поля возвращается весь остаток
"reason": "покупатель отказался",
"external_id": "your-refund-1", // опционально, ваш ключ идемпотентности
"signature": "…" // подписываются поля выше + id из пути
}
Ответ: { refund_id, refunded_total, status, refund_status, debt_added }.
refund_status — состояние самого возврата: pending означает,
что сумма уже удержана с вашего баланса, а покупателю уйдёт после обработки
оператором; done — деньги покупателю отправлены. Если возврат не
удастся, придёт вебхук payment.refund_failed, удержание снимется и
платёж снова можно будет вернуть.
Комиссия платформы при возврате не возвращается: покупателю уходит вся сумма
платежа, а получали вы её за вычетом комиссии — разница остаётся на вас. Если
доступного остатка не хватило, разница попадает в debt_added и гасится
из следующих поступлений.
Повтор того же запроса безопасен. Одинаковые байты считаются одним и тем же
запросом: ретрай по таймауту вернёт тот же refund_id, а не спишет с вас
второй раз. Чтобы провести ДВА одинаковых возврата намеренно, передайте разные
external_id — поле входит в подпись, и запросы перестанут быть
одинаковыми.
POST на ваш Webhook URL при каждой смене статуса:
{
"event": "payment.paid",
"payment_id": 42,
"order_id": "order-123",
"amount": 29900, // ЗДЕСЬ — В КОПЕЙКАХ
"currency": "RUB",
"status": "paid",
"method": "sbp",
"provider": "…",
"is_test": false,
"refunded": 0, // сколько всего возвращено по платежу
"paid_at": "2026-08-03T12:00:00.000Z",
"signature": "…"
}
vnt_live_sec_…)
и сравнивайте amount с суммой заказа. Отвечайте HTTP 200 — иначе повторим доставку
по расписанию 1 мин → 5 мин → 15 мин → 1 ч → 3 ч → 6 ч.Зачисляйте заказ только по event: "payment.paid". У возврата своё
событие payment.refunded с полями refund_id и
refund_amount; при частичном возврате status остаётся
paid, а amount — исходной суммой платежа, потому что платёж не
изменился. Сколько денег вернулось, показывает refunded.
До включения боевого режима payment_url ведёт на тестовую страницу с кнопками
«Оплатить»/«Отклонить» — прогоните полный цикл, включая вебхук, без реальных денег.
Тестовые платежи помечены is_test: true и в статистику не попадают.
Баланс и заявки на вывод — в кабинете по персональной ссылке
/cabinet?t=…, которую выдаёт оператор. Выплата уходит только на реквизиты,
сохранённые оператором при проверке — сменить их через API нельзя.
Минимальная сумма — 100 ₽.
Чтение — обычной подписью vnt_live_sec_…, эти ручки денег не двигают:
GET https://ventapay.click/api/v1/balance?api_key=vnt_live_pub_…&signature=…
→ { available_rub, debt_rub, held_rub, in_reserve_rub, min_payout_rub,
payout_requisites, payout_api_enabled }
GET https://ventapay.click/api/v1/payouts?api_key=vnt_live_pub_…&signature=…
Создание заявки на вывод подписывается отдельным ключом
vnt_out_…, а не vnt_live_sec_…:
POST https://ventapay.click/api/v1/payout
{ "api_key": "vnt_live_pub_…", "amount": 500, "signature": "<подпись ключом vnt_out_…>" }
vnt_live_sec_… лежит в коде вашей
интеграции и участвует в каждом запросе о платежах — его утечка не должна открывать
доступ к деньгам. Ключ vnt_out_… выдаёт оператор по запросу, по умолчанию его
нет, и отозвать его можно, не трогая рабочую интеграцию. Деньги в любом случае уходят
только на реквизиты, сохранённые оператором.| Ключ | Для чего | Где живёт |
|---|---|---|
vnt_live_pub_… + vnt_live_sec_… | создание платежей, возвраты, чтение баланса, проверка подписи вебхука | в коде вашего сервиса |
vnt_out_… | только заявки на вывод по API | в коде бота, если выплаты автоматизированы |
vnt_cab_… (токен кабинета) | весь кабинет: настройки, ключи, выплаты | только у владельца, не в коде |
Разделение сделано намеренно: pk/sk участвуют в каждой операции и рискуют утечь
вместе с исходниками или логами. Утечка этих ключей позволит создавать платежи, но
не даст вывести деньги — для вывода нужен отдельный токен кабинета. При любом подозрении
на утечку сообщите оператору: ключи перевыпускаются мгновенно, старые сразу перестают работать.