Приём без нашей страницы (H2H)

Создайте платёж запросом со своего сервера и получите в ответ код СБП. Покупатель остаётся на вашем сайте: QR и кнопку «Оплатить в приложении» вы рисуете сами, наша страница оплаты не участвует.

API v1 · обновлено 2026-09-12

Когда это нужно

Обычная интеграция отдаёт покупателя на нашу страницу оплаты — это проще и не требует ничего, кроме редиректа. H2H нужен, когда оплата должна целиком происходить у вас: своё оформление, свой экран ожидания, свой QR внутри приложения или бота. Взамен вы берёте на себя показ кода, таймер и опрос статуса.

Сейчас так работает СБП. Приём картами в этом режиме готовится; до его запуска карты принимаются через страницу оплаты.

Обязательное условие: IP покупателя

В поле payerIp передавайте публичный адрес покупателя, а не адрес своего сервера. Банк в СБП оценивает по нему риск платежа и без него платёж отклоняет. При серверной интеграции мы видим только ваш сервер, поэтому адрес можем получить лишь от вас.

Берите его из заголовка вашего балансировщика (X-Forwarded-For, первый адрес) или из соединения с браузером. Адреса из приватных диапазонов (10.*, 192.168.*, 172.16–31.*, 127.*) мы отклоняем сразу. IPv6 принимается.

Одним запросом: счёт и код сразу

Добавьте к обычному созданию счёта поля method, payerIp и start: true — в ответе появится объект payment с кодом СБП.

Объект payment

ПолеОписание
qrСтрока для QR-кода. Нарисуйте её любой библиотекой QR и покажите покупателю. Это ссылка СБП, картинку мы не отдаём — так вы сами выбираете размер и оформление.
deeplinkТа же ссылка для кнопки «Оплатить в приложении банка» — на телефоне открывает приложение. На СБП совпадает с qr.
urlСтраница, на которую нужно отправить покупателя, если у метода нет QR. Для СБП — null.
startedAtКогда код выдан.
expiresAtСрок жизни кода — он короче срока счёта. Именно его показывайте в таймере, см. ниже.

payment: null означает, что платёж ещё не создан (счёт в статусе created) или уже закрыт. Поле startError непустое, если счёт создался, а платёж — нет: сам счёт при этом живой, его id сохранён, повторите /start.

Двумя запросами: счёт заранее, код по клику

Так удобнее, когда заказ оформляется раньше оплаты: счёт создаётся при оформлении, а код запрашивается в момент, когда покупатель выбрал способ. Срок жизни кода (минуты) тогда не тратится на размышления покупателя.

ПолеТипОписание
method обяз.sbpКакой платёж создавать. Если при создании счёта способ был зафиксирован, он должен совпадать.
payerIp обяз.stringПубличный адрес покупателя (выше).
payerEmailstringПочта покупателя, если нужна для чека.

Ответ — тот же счёт с объектом payment, что и при start: true.

Повторный вызов не выдаёт новый код

Пока платёж жив, /start с тем же способом возвращает тот же код. Это сделано намеренно: отменить уже выданный код у банка нечем, и каждый новый QR — это ещё один код, по которому покупатель может заплатить. Вызывайте /start по действию покупателя, а не в цикле.

Таймер и перевыпуск кода

Код СБП живёт минуты, а счёт — до суток. Показывайте покупателю payment.expiresAt, а не expiresAt счёта: иначе человек увидит часовой таймер, уйдёт пить чай и вернётся к мёртвому коду.

Когда код перестал действовать, счёт возвращается в статус created и остаётся годным: запросите /start ещё раз и покажите новый код. Так можно сделать до трёх раз, дальше счёт закрывается статусом failed — создайте новый. Проверять, что код умер, нужно по статусу счёта: пока он pending, действует выданный код.

Итог платежа

Источник истины — вебхук invoice.paid. Опрос нужен для экрана ожидания: пока покупатель смотрит на QR, спрашивайте статус счёта не чаще одного раза в 3 секунды.

Товар отдавайте по статусу paid. Статусы failed и expired — финальные, схема переходов та же, что у обычной интеграции.

Ошибки

КодКогдаЧто делать
payer_ip_requiredstart: true без payerIp.Передайте адрес покупателя.
payer_ip_invalidПриватный адрес или адрес вашего сервера.Возьмите реальный адрес из заголовка балансировщика.
validationstart: true без method.Укажите способ оплаты.
method_unavailableСпособ не подключён проекту или временно недоступен.Предложите покупателю другой способ; доступные — в GET /methods.
invoice_expiredСрок счёта истёк.Создайте новый счёт.
invoice_closedСчёт уже оплачен, отклонён или истёк.Проверьте статус; для оплаченного ничего делать не нужно.
provider_unavailableПлатёж не удалось создать прямо сейчас.Повторите /start через несколько секунд или предложите другой способ.
not_foundСчёт не ваш или ключ другого режима (тестовый против боевого).Проверьте id и ключ.

Перед запуском

  • В payerIp уходит настоящий адрес покупателя — проверьте на реальном трафике, а не на localhost.
  • Таймер у покупателя считает по payment.expiresAt.
  • /start вызывается по действию покупателя, не в цикле и не на каждый опрос статуса.
  • Статус опрашивается не чаще раза в 3 секунды, а товар отдаётся по вебхуку.
  • Сценарий «код умер, покупатель хочет ещё раз» реализован: счёт вернулся в created → новый /start.
  • Всё проверено тестовым ключом: там код выдаёт эмулятор, деньги не двигаются.