На всю платёжную систему. Мерчант указывается в запросе платежа.
Платежи в вашем
продукте.
Подключайте мерчантов, получайте платёжные ссылки СБП и управляйте выплатами через единый API Payso.
https://api.payso.tech/v1
Зачисления, холды и заявки на выплату учитываются в рублях.
Показывайте клиенту QR или открывайте ссылку СБП в своём интерфейсе.
Перед первым запросом
- Получите доступ к кабинету у администратора Payso. При первом входе смените временный пароль и подключите 2FA.
-
В разделе «Мерчанты» скопируйте
idподключённого мерчанта. Идентификатор Payso отличается от номера кассы в вашей системе. - В разделе «API и интеграция» создайте API-ключ и секрет подписи. Они показываются один раз.
- Укажите HTTPS-адрес для webhook и создайте первый платёж.
API работает с реальными платежами и выплатами. Отдельного режима sandbox в этой версии нет.
Авторизация и подпись
Все запросы требуют заголовок
Authorization: Bearer <API_KEY>
. Тело запросов — JSON в UTF-8. Денежные значения передавайте строкой
с точкой, например
"2000.00"
, или JSON-числом. Ответы с денежными суммами преимущественно
используют строки.
Для каждого
POST
дополнительно передавайте
X-Payso-Timestamp
— Unix timestamp в секундах — и
X-Payso-Signature
. Допускается отклонение времени не более 5 минут.
signature = HMAC-SHA256(API_SECRET,
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + RAW_BODY
).hex_lowercase()
METHOD = POST
PATH = /v1/payments
В подпись входит публичный путь
/v1/…
без домена и query string. Подписывайте точные байты JSON, которые
отправляете: изменение пробелов, порядка полей или суммы меняет
подпись. GET-запросам достаточно Bearer-ключа. Не используйте ключи в
браузере клиента.
Пример на Node.js
import { createHmac } from 'node:crypto';
const path = '/v1/payments';
const timestamp = String(Math.floor(Date.now() / 1000));
const raw = JSON.stringify({
merchantId: '11111111-1111-4111-8111-111111111111',
externalId: 'order-1001',
amount: '1000.00',
description: 'Заказ №1001'
});
const signature = createHmac('sha256', process.env.PAYSO_API_SECRET)
.update(`POST\n${path}\n${timestamp}\n${raw}`, 'utf8')
.digest('hex');
const response = await fetch(`https://api.payso.tech${path}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PAYSO_API_KEY}`,
'Content-Type': 'application/json',
'X-Payso-Timestamp': timestamp,
'X-Payso-Signature': signature
},
body: raw
});
const payment = await response.json();
if (!response.ok) throw new Error(payment.message);
console.log(payment.id, payment.status, payment.sbpUrl);
Лимит — 600 запросов в минуту на платёжную систему. Максимальный размер тела — 64 КБ. Перевыпуск ключа сразу отзывает старый ключ для новых запросов.
Подключённые мерчанты
/v1/merchants
Мерчантов подключает администратор Payso. Ответ содержит только подключения вашей платёжной системы.
{
"items": [{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Магазин",
"url": "https://shop.example",
"max_amount": 100000.00,
"active": true,
"hold_hours": 24
}]
}
Минимальная сумма входящего платежа — 1 ₽. Максимальная —
max_amount
конкретного подключения. Холды задаются условиями подключения; после
успешной оплаты они отображаются на балансе.
Создание H2H-платежа
/v1/payments
Подпись обязательна
| Поле | Формат | Описание |
|---|---|---|
| merchantId * | UUID | ID из списка мерчантов Payso. |
| externalId * | string, 1–190 | Ваш уникальный ID заказа в пределах всей платёжной системы. |
| amount * | decimal | Сумма в RUB, до 2 знаков после точки. От 1 ₽ до лимита мерчанта. |
| description * | string, 1–255 | Описание покупки. |
| callbackUrl | HTTPS URL | До 500 символов. Если не указан, используется webhook из кабинета. |
{
"id": "22222222-2222-4222-8222-222222222222",
"merchantId": "11111111-1111-4111-8111-111111111111",
"merchant": "Магазин",
"externalId": "order-1001",
"amount": "1000.00",
"netAmount": "962.00",
"currency": "RUB",
"status": "pending",
"description": "Заказ №1001",
"sbpUrl": "https://qr.nspk.ru/…",
"createdAt": "2026-09-17T09:00:00.000+00:00",
"paidAt": null,
"expiresAt": "2026-09-17T13:00:00.000+00:00"
}
amount
— сумма покупателю,
netAmount
— зачисление после удержания 3,8%, с округлением до копеек.
Отображайте клиенту QR из
sbpUrl
или открывайте эту ссылку. Подтверждайте покупку только по статусу
paid
.
HTTP 202, status=creating
означает, что ссылка ещё не получена. Сохраните
id
и
externalId
, опрашивайте GET платежа раз в 3–5 секунд или ждите
payment.ready
. Таймаут не означает отказ. Не создавайте для повтора новый
externalId.
Счёт существует 4 часа. Срок действия банковской ссылки может быть
короче. При подтверждённой поздней оплате статус может перейти из
expired
в
paid
.
Статусы и история платежей
/v1/payments/{id}
Возвращает один платёж в формате выше.
id
— UUID Payso, не externalId.
/v1/payments?page=0&status=paid&query=order-1001
Страница отсчитывается от 0, по 25 записей. Параметры необязательны.
status
:
paid
,
expired
,
pending
или пусто (все). Фильтр pending включает ещё создаваемые платежи.
query
ищет externalId или точный UUID платежа.
{ "items": [], "total": 0, "pageSize": 25 }
| Статус | Значение |
|---|---|
| creating | Ссылка СБП ещё не получена; результат создания уточняется. |
| pending | Ссылка готова, ожидается оплата. |
| paid | Оплата подтверждена. |
| expired | Истёк срок счёта. |
Общий баланс
/v1/balance
{
"currency": "RUB",
"available": "48000.00",
"hold": "9620.00",
"frozen": "0.00",
"reserved": "2000.00",
"total": "57620.00"
}
available
— доступно к новой выплате;
hold
— ещё не созревшие зачисления;
frozen
— замороженные средства;
reserved
— сумма заявок в работе. Резерв уже вычтен из available.
total = available + hold + frozen
, без зарезервированных выплат.
Карты РФ и USDT TRC20
2 000–100 000 ₽. Комиссия 0 ₽. Отправка после подтверждения администратором.
500–500 000 ₽. Из суммы удерживается 240 ₽. Исполнение вручную.
Суммы выплат — целые рубли. Для TRC20 используется фиксинг Rapira на 09:00 МСК без наценки. Котировка фиксируется при создании заявки.
/v1/rates
Текущий фиксинг, лимиты и комиссии:
{
"rapira": "80.00", "fixing": "09:00 Europe/Moscow", "markupPercent": "0",
"card": { "min": 2000, "max": 100000, "feeRub": 0 },
"usdt_trc20": { "min": 500, "max": 500000, "feeRub": 240 }
}
/v1/payouts/quote
Предварительный расчёт без списания:
Запрос: { "method": "usdt_trc20", "amount": "8240" }
Ответ: {
"method": "usdt_trc20", "amount": "8240.00", "feeRub": "240.00",
"receiveAmount": "100.000000", "receiveCurrency": "USDT", "rate": "80.0"
}
/v1/payouts
| Поле | Описание |
|---|---|
| externalId * | Уникальный ID выплаты, 1–190 символов, в пределах платёжной системы. Пространство ID выплат отдельно от платежей. |
| method * |
card
или
usdt_trc20
.
|
| amount * | Общая сумма списания в целых рублях. |
| address * | Номер карты 16–19 цифр без пробелов или адрес TRON. |
| expectedRate |
Верните точное значение
rate
из расчёта. Если курс изменился, получите HTTP 409 и сможете
подтвердить новый расчёт. Для карты — null.
|
| callbackUrl | HTTPS-адрес уведомлений. По умолчанию — из кабинета. |
{
"externalId": "payout-1001", "method": "card",
"amount": "2000", "address": "4111111111111111"
}
Номер в примере иллюстрирует формат. Используйте реальные реквизиты получателя.
{
"id": "33333333-3333-4333-8333-333333333333",
"externalId": "payout-1001", "method": "card",
"amount": "2000.00", "feeRub": "0.00", "currency": "RUB",
"receiveAmount": "2000.00", "receiveCurrency": "RUB",
"rate": null, "destination": "•••• 1111", "status": "pending",
"processing": null, "txHash": null,
"createdAt": "2026-09-17T09:00:00.000+00:00"
}
Средства резервируются сразу.
pending
— ожидает рассмотрения;
approved
— подтверждена, отправляется;
paid
— исполнена;
rejected
— отклонена, резерв освобождён.
processing=unknown
у карты означает сверку результата, резерв сохраняется. Не отправляйте
дублирующую заявку.
/v1/payouts/{id}
/v1/payouts?page=0
История:
{items, total, pageSize: 25}
. После ручной выплаты TRC20 появляется
txHash
.
Webhook
Payso отправляет POST JSON на указанный HTTPS-адрес. События:
payment.ready
,
payment.paid
,
payment.expired
,
payout.approved
,
payout.paid
,
payout.rejected
.
{
"eventId": "44444444-4444-4444-8444-444444444444",
"event": "payment.paid",
"id": "22222222-2222-4222-8222-222222222222",
"externalId": "order-1001",
"merchantId": "11111111-1111-4111-8111-111111111111",
"amount": "1000.00", "netAmount": "962.00",
"currency": "RUB", "status": "paid", "sbpUrl": "https://qr.nspk.ru/…"
}
Для выплат тело содержит
eventId, event, id, externalId, amount, feeRub, currency, status,
method, receiveAmount, txHash
. У карты receiveAmount в RUB, у TRC20 — в USDT.
Проверка подписи уведомления
Заголовки:
X-Payso-Event-Id
,
X-Payso-Timestamp
,
X-Payso-Signature
. Формула webhook отличается от подписи входящего API-запроса:
signature = HMAC-SHA256(API_SECRET, TIMESTAMP + "\n" + RAW_BODY).hex_lowercase()
Проверяйте подпись по исходному телу до JSON-разбора, сравнивайте в постоянное время. Проверяйте timestamp с допуском 5 минут. После перевыпуска ключа уже созданные операции продолжают использовать секрет, действовавший при их создании: сохраните его для завершения этих операций.
Обрабатывайте
eventId
идемпотентно. Ответьте любым HTTP 2xx после надёжного сохранения
события. При ошибке Payso повторяет доставку до 20 попыток с растущим
интервалом до часа. Дубли и доставка не по порядку возможны; при
сомнении запросите актуальное состояние через GET. Не понижайте уже
подтверждённый paid из-за старого уведомления.
Ошибки и повторные запросы
{ "message": "Недостаточно доступного баланса" }
| HTTP | Что означает |
|---|---|
| 400 | Параметры, лимит суммы, реквизиты или недостаточный баланс. |
| 401 | Неверный ключ, подпись или timestamp. |
| 403 | Доступ платёжной системы или мерчанта отключён. |
| 404 | Операция или мерчант не найдены в вашей платёжной системе. |
| 409 | externalId уже занят другими параметрами либо изменилась подтверждаемая котировка. |
| 413 | Тело превышает 64 КБ. |
| 429 | Превышен лимит запросов. |
| 500 / 503 | Временная ошибка или недоступность приёма, выплаты, курса. |
Повтор создания с тем же externalId и теми же параметрами возвращает прежнюю операцию. Другие параметры с тем же ID дают 409. При сетевом таймауте повторяйте исходный запрос с тем же externalId и обновлённой подписью/timestamp; для уже известного ID используйте GET.
Идентификаторы externalId уникальны на всю платёжную систему, а не на отдельного мерчанта. Включайте идентификатор мерчанта в свой номер заказа, если нумерация у магазинов пересекается. У каждой операции сохраняется исходный адрес уведомлений.