API REFERENCE · V1 OpenAPI 3.1 ↓
СБП · H2H

Платежи в вашем
продукте.

Подключайте мерчантов, получайте платёжные ссылки СБП и управляйте выплатами через единый API Payso.

BASE URL https://api.payso.tech/v1
Один ключ

На всю платёжную систему. Мерчант указывается в запросе платежа.

Общий баланс в ₽

Зачисления, холды и заявки на выплату учитываются в рублях.

Без пейформы

Показывайте клиенту QR или открывайте ссылку СБП в своём интерфейсе.

Перед первым запросом

  1. Получите доступ к кабинету у администратора Payso. При первом входе смените временный пароль и подключите 2FA.
  2. В разделе «Мерчанты» скопируйте id подключённого мерчанта. Идентификатор Payso отличается от номера кассы в вашей системе.
  3. В разделе «API и интеграция» создайте API-ключ и секрет подписи. Они показываются один раз.
  4. Укажите HTTPS-адрес для webhook и создайте первый платёж.

API работает с реальными платежами и выплатами. Отдельного режима sandbox в этой версии нет.

01 / ПОДКЛЮЧЕНИЕ

Авторизация и подпись

Все запросы требуют заголовок 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 КБ. Перевыпуск ключа сразу отзывает старый ключ для новых запросов.

02 / МЕРЧАНТЫ

Подключённые мерчанты

GET /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 конкретного подключения. Холды задаются условиями подключения; после успешной оплаты они отображаются на балансе.

03 / ПРИЁМ

Создание H2H-платежа

POST /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 .

Статусы и история платежей

GET /v1/payments/{id}

Возвращает один платёж в формате выше. id — UUID Payso, не externalId.

GET /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 Истёк срок счёта.
04 / СРЕДСТВА

Общий баланс

GET /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 , без зарезервированных выплат.

05 / ВЫПЛАТЫ

Карты РФ и USDT TRC20

Карта РФ

2 000–100 000 ₽. Комиссия 0 ₽. Отправка после подтверждения администратором.

USDT TRC20

500–500 000 ₽. Из суммы удерживается 240 ₽. Исполнение вручную.

Суммы выплат — целые рубли. Для TRC20 используется фиксинг Rapira на 09:00 МСК без наценки. Котировка фиксируется при создании заявки.

GET /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 }
}
POST /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"
}
POST /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 у карты означает сверку результата, резерв сохраняется. Не отправляйте дублирующую заявку.

GET /v1/payouts/{id}
GET /v1/payouts?page=0

История: {items, total, pageSize: 25} . После ручной выплаты TRC20 появляется txHash .

06 / УВЕДОМЛЕНИЯ

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 из-за старого уведомления.

07 / НАДЁЖНОСТЬ

Ошибки и повторные запросы

{ "message": "Недостаточно доступного баланса" }
HTTP Что означает
400 Параметры, лимит суммы, реквизиты или недостаточный баланс.
401 Неверный ключ, подпись или timestamp.
403 Доступ платёжной системы или мерчанта отключён.
404 Операция или мерчант не найдены в вашей платёжной системе.
409 externalId уже занят другими параметрами либо изменилась подтверждаемая котировка.
413 Тело превышает 64 КБ.
429 Превышен лимит запросов.
500 / 503 Временная ошибка или недоступность приёма, выплаты, курса.

Повтор создания с тем же externalId и теми же параметрами возвращает прежнюю операцию. Другие параметры с тем же ID дают 409. При сетевом таймауте повторяйте исходный запрос с тем же externalId и обновлённой подписью/timestamp; для уже известного ID используйте GET.

Идентификаторы externalId уникальны на всю платёжную систему, а не на отдельного мерчанта. Включайте идентификатор мерчанта в свой номер заказа, если нумерация у магазинов пересекается. У каждой операции сохраняется исходный адрес уведомлений.