Документация API

Lave Pay · API для мерчантов

Принимайте оплату с балансов Laveshka и по сети, платите с баланса мерчанта — людям, на карты, по QR, в блокчейн — и узнавайте об исходе подписанным вебхуком. Один ключ, один базовый URL, JSON.

«Скопировать для ИИ» кладёт в буфер всю документацию как Markdown — вставьте её в ChatGPT, Claude или Cursor, и ассистент напишет интеграцию за вас. Примеры в документации — на curl и Python; выбранный язык запоминается.

Начало

Обзор

Мерчант создаётся в мини-приложении Laveshka (Сервисы → Для бизнеса → Lave Pay). Там же выпускается API-ключ, задаётся вебхук и виден журнал всех доставок. Дальше всё делает ваш сервер через REST API. Базовый сценарий — четыре шага:

  1. Сервер выставляет счёт — POST /invoices — и получает pay_url.
  2. Вы отдаёте ссылку покупателю: кнопкой в боте, ссылкой на сайте, QR-кодом.
  3. Покупатель открывает её — мини-приложение показывает экран оплаты — и платит одним нажатием.
  4. Сумма за вычетом комиссии ложится на баланс мерчанта, ваш сервер получает вебхук invoice.paid.

Баланс мерчанта — отдельный счёт. На него приходят оплаты, депозиты клиентов и купленные USDT; с него уходят возвраты и всё, что ваш сервер тратит по API. Владелец переводит его на свой основной баланс кнопкой в консоли — до основного баланса API-ключ дотянуться не может.

Карточка API

Базовый URLhttps://<домен>/merchant/v1
АвторизацияX-Api-Key: LAVE-<merchant_id>-<secret> или Authorization: Bearer …
ФорматJSON; суммы — строки. amount, fee, net — оценка в USD с двумя знаками; поля активов — в единицах своего актива
Конверт ответа{"ok": true, "data": …} / {"ok": false, "error": {"code", "message", "details"}}
Лимит запросов300 в минуту на ключ — остальные лимиты
Идентификаторыс префиксом объекта: inv_, addr_, dep_, tr_, chq_, qr_, cp_, sbp_, st_, tg_, wd_, buy_, evt_; qrq_ — цена QR из POST /qr/check, op_ — строка истории баланса
ВремяISO 8601 с зоной: 2026-08-22T09:10:00+00:00
Подпись вебхуковHMAC-SHA256(secret, "<timestamp>.<raw body>") — проверка
БиблиотекиPython — lavepay

Быстрый старт

  1. Создайте мерчанта в приложении. API-ключ показывается один раз сразу после создания: на сервере хранится только хеш, потерянный ключ не восстанавливается, а выпускается новый (Настройки → Выпустить новый ключ; старый перестаёт работать мгновенно).
  2. Выставьте счёт и получите pay_url:
bash
# ключ вида LAVE-12-k8Vn3pQ… — 12 здесь merchant_id
export LAVE_KEY="LAVE-12-k8Vn3pQxxxxxxxxxxxxxxxxxxxx"

curl -X POST "https://<домен>/merchant/v1/invoices" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "12.34", "description": "Заказ №1042", "order_id": "ord-1042"}'
Python · pip install lavepay
import asyncio
from lavepay import LavePay

async def main():
    async with LavePay("LAVE-12-k8Vn3pQxxxxxxxxxxxxxxxxxxxx") as lp:
        invoice = await lp.invoices.create(
            "12.34", order_id="ord-1042", description="Заказ №1042",
        )
        print(invoice.pay_url)

asyncio.run(main())
  1. Отдайте pay_url покупателю. Это ссылка вида https://t.me/laveshka?startapp=lpay_…: она открывает мини-приложение сразу на экране оплаты.
  2. Узнайте об оплате. Быстрый путь — вебхук invoice.paid (URL задаётся в приложении, маршрут проверяется POST /webhook/test). Запасной — опрос GET /invoices/{id}. Товар выдавайте по status: paid, сверив order_id и сумму.

Ключ можно проверить прямо здесь: нажмите «Попробовать» у GET /me и введите его в окне — запрос уйдёт из вашего браузера в API этого же домена. Так же пробуется любой эндпоинт на этой странице.

Библиотеки

Запросы можно не писать руками. Пока библиотека одна, для Python; на других языках работайте с REST API напрямую — в примерах ниже рядом с curl есть вкладка Python.

Python

lavepay · Python 3.11+ · MIT

bash
pip install lavepay
pip install "lavepay[fastapi]"   # приём вебхука в FastAPI
pip install "lavepay[aiogram]"   # кнопка оплаты в боте
pip install "lavepay[aiohttp]"   # вебхук на aiohttp
pip install "lavepay[qr]"        # PNG с QR-кодом ссылки

В библиотеке — счета и история, переводы и чеки, оплата QR и вывод на карту, Steam, Stars и Premium, покупка USDT, вебхуки: проверка подписи, дедупликация по id события и опрос статусов, если вебхук поднимать негде (lp.start_polling()). Суммы передавайте строкой или Decimal: float библиотека не примет. Исключения повторяют error.code ответа: InsufficientFundsError, RateChangedError, QuoteExpiredError и остальные. Адреса клиентов и вывод в блокчейн библиотека пока не покрывает — для них REST напрямую.

Основы

Авторизация

Каждый запрос несёт ключ в заголовке — любым из двух способов:

http
GET /merchant/v1/me
X-Api-Key: LAVE-12-k8Vn3pQxxxxxxxxxxxxxxxxxxxx

# или
Authorization: Bearer LAVE-12-k8Vn3pQxxxxxxxxxxxxxxxxxxxx

Ключ вида LAVE-<merchant_id>-<secret> выпускается в приложении и показывается один раз. У каждого мерчанта свой ключ, у одного аккаунта — до десяти мерчантов; ключи и их деньги не пересекаются. Утёкший ключ меняют кнопкой Выпустить новый ключ — старый гаснет мгновенно.

GET/me

Кто я и что лежит на балансе мерчанта. Заодно — проверка, что ключ работает.

200 OK
{
  "ok": true,
  "data": {
    "id": 12,
    "name": "Pixel Shop",
    "description": "Стикеры и наборы",
    "project_url": "https://pixel.shop",
    "avatar_url": "https://…/avatar.jpg?v=3",
    "status": "active",
    "fee_bps": 0,
    "payouts_enabled": false,
    "webhook": { "state": "healthy", "url": "https://pixel.shop/lavepay", … },
    "balance": "1532.20",
    "currency": "USD",
    "balances": {
      "USD": "1532.20",
      "GRAM": "0.2",
      "TRX": "0",
      "SOL": "0",
      "BNB": "0.025",
      "ETH": "0"
    },
    "created_at": "2026-08-22T09:00:00+00:00"
  }
}
ПолеТипЧто это
statusстрокаactive или paused — на паузе мерчант не выставляет счета и не тратит
fee_bpsчисловаша ставка с оплаты балансом Laveshka в базисных пунктах (0 = 0 %) — см. комиссии
payouts_enabledboolвключён ли переключатель Выплаты по API — без него все траты с баланса отвечают 403
webhookобъектсостояние endpoint'а, как в GET /webhook
balancesобъектбалансы мерчанта по активам. USDT лежит под ключом USD; оплата балансом Laveshka или по сети остаётся в уплаченной монете, CryptoBot и xRocket рассчитываются в USDT и попадают на USD
balance, currencyстрокаUSD-баланс и его код — для старых интеграций; то же, что balances.USD

Ответы и ошибки

Все ответы завёрнуты в единый конверт. Успех — {"ok": true, "data": …}; ошибка — {"ok": false, "error": {"code", "message", "details"}}, где error.code — стабильный код, на который можно завязать логику, а message — текст для людей. В примерах ниже конверт для краткости опущен.

пример ошибки
{
  "ok": false,
  "data": null,
  "error": {
    "code": "rate_limited",
    "message": "Rate limit is 300 requests per minute",
    "details": { "retry_after": 12 }
  }
}
HTTPerror.codeКогда
401unauthorizedключа нет, он неверный или мерчант удалён; мерчант или аккаунт отключён или ограничен — обратитесь в поддержку
403forbiddenмерчант на паузе; выплаты по API выключены; клиент адреса не прошёл AML (details.reason: aml)
403buy_blockedпокупка USDT временно закрыта: слишком много неоплаченных и снятых покупок; до details.until
404not_foundобъект, событие или получатель не найден
409duplicate_operationorder_id уже занят счётом с другой суммой или другим методом
409insufficient_fundsна балансе мерчанта не хватает: на возврат, выплату, цену QR на проверке (POST /qr/check), вывод в блокчейн — «Недостаточно USDT на балансе мерчанта» (или GRAM, TRX, SOL, BNB, ETH); ничего не списано
409limit_exceededпотолок: 1000 открытых счетов, 100 000 адресов клиентов, 10 покупок USDT в работе
409quote_expiredцена QR истекла, а QR стал дороже — новая цена в details.quote
409rate_changedсписание вышло дороже max_debit (карта, блокчейн, Steam, Stars/Premium) или курс покупки USDT выше max_rate; ничего не списано
409payout_unavailableоплата QR, вывод на карту или вывод по этой сети временно недоступны — попробуйте позже; ничего не списано
409idempotency_conflictтот же idempotency_key уже стоит за другой операцией
422validation_errorплохое тело запроса; details.errors — что именно; у сумм — details.minimum/maximum
422steam_account_rejectedтакого логина Steam нет или его нельзя пополнить; ничего не списано
429rate_limitedпревышен лимит; details.retry_after — через сколько секунд можно
502provider_errorвнешняя сторона не ответила до списания (Fragment у Stars/Premium, реквизиты бесшовного счёта); ничего не списано
503steam_unavailable / steam_pausedпополнение Steam сейчас недоступно или на паузе; ничего не списано
503buy_unavailableпокупка USDT за рубли выключена или временно недоступна; ничего не создано
503provider_not_configuredнаправление временно выключено на сервере: Lave Pay целиком, оплата по сети или монете, QR, карты, блокчейн, Stars/Premium
Отказ до списания — ошибка ответа, отказ после — объект со статусом failed. У всех трат с баланса мерчанта одно правило: ошибка 4xx/5xx значит, что ничего не списано и операции нет. Если же деньги уже удержаны, а банк, сеть или поставщик отказали, ответ — 200 с объектом failed, причина в error, деньги уже вернулись на баланс мерчанта, и приходит вебхук *.failed. Логику завязывайте на status, текст error — для людей.

Идемпотентность

Сетевая ошибка на записи — таймаут, обрыв, 5xx — не означает, что операции не было. Повторяйте тот же запрос с тем же ключом, а не новый: повтор вернёт уже сделанное и второй раз ничего не спишет. Новый ключ после неясного исхода может отправить деньги дважды.

ЧтоКлючПовтор с тем же ключомТот же ключ, другие данные
Счётorder_id (необязателен, но лучше задавать)тот же счёт — если сумма и метод те же409 duplicate_operation
Перевод, чекidempotency_key обяз.та же операция; чек читается по ключу: GET /cheques/{ключ}—
Оплата QR, вывод на карту, Steam, Stars/Premium, вывод в блокчейн, покупка USDTidempotency_key обяз.тот же объект с replayed: true; объект читается по ключу: GET …/{ключ}. Повтор узнаётся раньше всех проверок — даже если выплаты с тех пор выключили409 idempotency_conflict
Вебхукid события (evt_…)повтор несёт тот же id и то же тело — дедуплицируйте по нему—

Ключ — до 64 символов, уникален в рамках мерчанта. Собирайте его из своего заказа (payout-4665), а не случайно: тогда повтор после сбоя совпадёт сам. Отказ до списания (422, 409, 403, 429, 503) ключ не расходует. Два мерчанта одного владельца с одинаковыми ключами не пересекаются.

Списки и пагинация

Все списки устроены одинаково: курсорная пагинация, новые первыми. limit — до 200, по умолчанию 50; before — next_cursor из предыдущей страницы; на последней странице его нет (null). Фильтр status принимает одно значение.

bash
# оплаченные счета по 50, вторая страница — before из next_cursor первой
curl "https://<домен>/merchant/v1/invoices?status=paid&limit=50&before=4192" \
  -H "X-Api-Key: $LAVE_KEY"
200 OK
{ "ok": true, "data": { "items": [ … ], "next_cursor": 4192 } }
СписокФильтрыstatus
GET /invoicesstatus, order_idactive paid expired cancelled refunded
GET /addressesclient_id, network—
GET /depositsaddress_id, client_id, statuspending processing completed failed
GET /chequesstatusactive completed cancelled
GET /qr/payments, /card/payouts, /sbp/payouts, /steam/orders, /withdrawalsstatuspending processing completed failed
GET /telegram/ordersstatus, serviceте же; service — stars или premium
GET /buy/ordersstatuspending awaiting_payment overdue completed cancelled
GET /webhook/deliveriesstatuspending delivered failed
GET /history—— (история баланса)

Лимиты

Каждое действие, которое заставляет сервер Laveshka отправить запрос наружу, метится счётчиком — Lave Pay нельзя превратить в источник трафика на чужой адрес. Превышение — 429 rate_limited с details.retry_after.

ЧтоЛимит
Запросы к API300 / мин на ключ
Отклонённые ключиограничены с одного адреса; валидный ключ с того же адреса продолжает работать
Новые адреса клиентов (POST /addresses)60 / мин на мерчанта; повтор существующего не в счёт
Проверки QR (POST /qr/check и перепроверка истёкшей цены)20 / мин на мерчанта
Выводы на карту (POST /card/payouts)60 / мин на мерчанта
Выводы по СБП (POST /sbp/payouts)60 / мин на мерчанта
Заказы Steam (POST /steam/orders)котировки и заказы ограничены на мерчанта
Покупки Stars и Premium (POST /telegram/orders)30 / мин на мерчанта
Выводы в блокчейн (POST /withdrawals)30 / мин на мерчанта; повтор с тем же ключом не в счёт
Сети и котировка вывода (GET /withdrawals/networks, /withdrawals/quote)60 / мин на мерчанта на двоих
Покупки USDT за рубли (POST /buy/orders)20 / мин на мерчанта
Курс (GET /qr/rate, /card/rate, /sbp/rate, /sbp/banks, /steam/rate, /telegram/rate, /buy/rate)60 / мин на мерчанта по каждому направлению
POST /webhook/test6 / мин и 60 / час; сверх — событие не создаётся
Повтор события вручную30 / мин на мерчанта
Смена URL вебхука30 / час на мерчанта
Создание мерчантов20 / день, 10 живых на аккаунт

Потолки объёма — не про частоту, а про количество: 1000 открытых счетов, 100 000 адресов клиентов, 10 покупок USDT в работе на мерчанта; сверх — 409 limit_exceeded.

Комиссии и суммы

Комиссия с оплаты счёта зависит от того, чем заплатил покупатель; способ приходит в paid_via счёта, округление вверх до цента. Персональная ставка мерчанта (fee_bps в GET /me), если назначена, заменяет ставки баланса и перевода по сети; у CryptoBot и xRocket ставка всегда своя.

Способ оплатыpaid_viaКомиссия
Баланс Laveshkabalance0 % — ваша ставка в fee_bps
USDT по сети (TRC-20, BEP-20, ERC-20, GRAM, Solana)chain0.1 %
GRAM, TRX, SOL, BNB и ETH по своей сетиchain0.05 %
CryptoBot (мультивалютно)cryptobot3.1 %: 3 % комиссия CryptoBot + 0.1 % service
xRocket (мультивалютно)xrocket1.5 % — комиссия xRocket, без service

Депозит на адрес клиента и недоплата бесшовного счёта берут ту же ставку перевода по сети.

ОперацияСуммы и срокиКомиссия
Счёт0.10 … 10 000 USD; срок жизни 1 час по умолчанию, 60 с … 7 дней (ttl)по способу оплаты, см. выше
Перевод пользователюот 0.10 $нет
Чек0.10 … 100 000 $ за активацию, до 100 000 активаций, не больше 1 000 000 $ на чекнет
Оплата QR1 … 400 000 ₽ за оплату, в USDT с баланса мерчантавнутри курса «₽ за 1 USDT»
Вывод на карту100 … 300 000 ₽ за выплату, в USDT с баланса мерчантавнутри курса; персональная ставка мерчанта, если назначена, уже в GET /card/rate
Вывод по СБП500 … 100 000 ₽ за перевод, в USDT с баланса мерчантавнутри курса (GET /sbp/rate)
Steam1 … 1000 $ за заказ, точная цена — в момент заказавнутри цены
Stars и Premiumпо курсу Laveshka (GET /telegram/rate)внутри цены
Вывод в блокчейнот min_amount сети до 10 000 $ за вывод, в монете выводасверху суммы, своя у каждой сети (GET /withdrawals/networks)
Покупка USDT за рублиот 500 ₽, потолок одной покупки у каждого мерчанта свой (max_rub); до 10 покупок в работевнутри курса (GET /buy/rate)

Приём оплаты

Счета

Счёт — просьба заплатить. Обычный счёт отдаёт pay_url: покупатель открывает мини-приложение и платит балансом Laveshka, переводом по сети или через CryptoBot и xRocket — чем захочет. Бесшовный счёт вместо ссылки сразу отдаёт реквизиты перевода по выбранной сети — для своего экрана оплаты.

POST/invoices

Выставить счёт.

bash
curl -X POST "https://<домен>/merchant/v1/invoices" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "12.34",
    "description": "Набор стикеров «Лето»",
    "order_id": "ord-1042",
    "payload": "{\"cart\":7,\"user\":99}",
    "return_url": "https://pixel.shop/thanks?o=1042",
    "ttl": 1800
  }'
Python
invoice = await lp.invoices.create(
    "12.34",
    description="Набор стикеров «Лето»",
    order_id="ord-1042",
    payload='{"cart":7,"user":99}',
    return_url="https://pixel.shop/thanks?o=1042",
    ttl=1800,
)
print(invoice.pay_url)
ПолеТипЧто это
amount обяз.строкав currency: USD и USDT — до двух знаков после точки, монета — до своей точности. 0.10 … 10 000 USD
currencyUSDв чём записана сумма: USD, USDT, GRAM, TRX, SOL, BNB или ETH. Счёт в монете стоит своих монет, а не долларов
accept["USDT"]чем можно заплатить: USDT, GRAM, TRX, SOL, BNB, ETH. Зачисление — в монете оплаты. Переводом по сети USDT принимается по TRC-20, BEP-20, ERC-20 (Ethereum), GRAM и Solana; GRAM, TRX, SOL, BNB и ETH — каждая по своей сети
descriptionдо 256видит покупатель на экране оплаты
order_idдо 128ваш номер заказа; уникален в рамках мерчанта и работает как ключ идемпотентности
payloadдо 1024непрозрачная строка, возвращается как есть в ответах и вебхуках
return_urlhttp(s), до 512кнопка «Вернуться» после оплаты; пусто — берётся ссылка проекта
ttl60 … 604800секунд до истечения; по умолчанию час
method{asset, network}бесшовный счёт: вместо ссылки сразу реквизиты перевода — ниже

Объект счёта

Один и тот же объект приходит в ответ на создание, в GET и в data вебхуков.

200 OK
{
  "id": "inv_87c8af4c-9200-45af-9bf0-d322e4ac45b5",
  "merchant_id": 12,
  "status": "active",
  "amount": "12.34",
  "fee": "0.00",
  "net": "12.34",
  "currency": "USD",
  "face_amount": "12.34",
  "accept": ["USDT"],
  "paid_via": null,
  "paid_asset": null,
  "paid_amount": null,
  "payer": null,
  "description": "Набор стикеров «Лето»",
  "order_id": "ord-1042",
  "payload": "{\"cart\":7,\"user\":99}",
  "return_url": "https://pixel.shop/thanks?o=1042",
  "pay_url": "https://t.me/laveshka?startapp=lpay_Xyug2fo4Mwuj54Wd",
  "method": null,
  "payment": null,
  "created_at": "2026-08-22T09:10:00+00:00",
  "expires_at": "2026-08-22T09:40:00+00:00",
  "paid_at": null,
  "refunded_at": null
}
ПолеТипЧто это
statusстрокаactive → paid → refunded; из active также expired (вышел ttl, а перевод по счёту не найден) и cancelled (ваш вызов)
amount, fee, netUSDоценка в долларах. При создании считаются по ставке оплаты балансом — это и есть цифры, если покупатель платит балансом. При оплате по сети или через кошелёк окончательные fee и net считаются по ставке этого способа в момент оплаты и приходят в invoice.paid
currency, face_amountстрокав чём назван счёт и сама названная сумма: у счёта в монете face_amount — монеты, amount — их оценка в USD
acceptсписокчем разрешено платить
paid_viaстрокачем заплатили: balance, chain, cryptobot или xrocket; null, пока не оплачен. По нему берётся ставка комиссии
paid_asset, paid_amountстрокав какой монете и сколько пришло на баланс мерчанта; это же вернётся покупателю при возврате
payerобъектпосле оплаты — покупатель: {"telegram_id": 123456789, "username": "durov"} (username может быть null); у перевода по сети — null, плательщик анонимен
pay_urlстрокассылка на экран оплаты; null у бесшовного счёта
method, paymentобъекттолько у бесшовного счёта: выбранный метод и реквизиты перевода
expires_at, paid_at, refunded_atвремякогда истекает, когда оплачен, когда возвращён

Получить счёт и список

GET/invoices/{id}

Один счёт по inv_…. Подходит для опроса статуса, если вебхук не настроен, и как запасной канал рядом с ним.

bash
curl "https://<домен>/merchant/v1/invoices/inv_87c8af4c-9200-45af-9bf0-d322e4ac45b5" \
  -H "X-Api-Key: $LAVE_KEY"
Python
invoice = await lp.invoices.get("inv_87c8af4c-9200-45af-9bf0-d322e4ac45b5")

# или подождать оплату опросом, если вебхука нет
invoice = await lp.invoices.wait(invoice.id, timeout=600)
if invoice.matches(amount="12.34", order_id="ord-1042"):
    ...
GET/invoices

Список: status (active|paid|expired|cancelled|refunded), order_id, limit, before — как во всех списках.

Отмена и возврат

POST/invoices/{id}/cancel

Отменить счёт. Только для active. Покупатель, открывший старую ссылку, увидит «счёт больше не действует». Отправляется invoice.cancelled.

POST/invoices/{id}/refund

Полный возврат. Только для paid. Покупателю возвращаются paid_amount в paid_asset: при оплате GRAM с баланса Laveshka это GRAM, у CryptoBot и xRocket — USDT. Комиссия Laveshka не возвращается. Если соответствующего баланса мерчанта не хватает, недостающее списывается с такого же актива на основном балансе владельца; не хватило и там — 409 insufficient_funds. Отправляется invoice.refunded.

Счёт, оплаченный прямым переводом по сети, автоматически не возвращается: плательщик такого перевода анонимен, метод ответит 422 validation_error.

bash
curl -X POST "https://<домен>/merchant/v1/invoices/inv_87c8af4c-…/cancel" \
  -H "X-Api-Key: $LAVE_KEY"

curl -X POST "https://<домен>/merchant/v1/invoices/inv_87c8af4c-…/refund" \
  -H "X-Api-Key: $LAVE_KEY"
Python
invoice = await lp.invoices.cancel("inv_87c8af4c-…")
invoice = await lp.invoices.refund("inv_87c8af4c-…")

Бесшовный счёт: реквизиты сразу

Обычный счёт отдаёт pay_url, и монету с сетью покупатель выбирает в мини-приложении. Бесшовный сразу отдаёт реквизиты перевода по выбранному методу — их показывают у себя. method.asset — USDT, GRAM, TRX, SOL, BNB или ETH; method.network — TRX, BSC, ETH, GRAM или SOL (читаются и TRC20/TRON, BEP20, ERC20/ERC-20/ETHEREUM, TON). USDT ходит по всем пяти сетям, GRAM — только по GRAM, TRX — по TRON, SOL — по Solana, BNB — по BSC, ETH — по Ethereum. В сети Ethereum {"asset": "USDT", "network": "ETH"} — это USDT ERC-20, {"asset": "ETH", "network": "ETH"} — сама монета ETH.

POST /invoices
{
  "amount": "18",
  "currency": "USDT",
  "order_id": "ord-7",
  "method": { "asset": "USDT", "network": "BSC" }
}
200 OK, фрагмент
"pay_url": null,
"method": { "asset": "USDT", "network": "BSC" },
"payment": {
  "address": "0x8c1d…",
  "amount": "18.003",
  "amount_units": "18003000",
  "asset": "USDT",
  "network": "BSC",
  "status": "waiting",
  "confirmations": 0,
  "confirmations_required": 0,
  "received": null,
  "tx_hash": null,
  "expires_at": "2026-10-06T13:10:00+00:00",
  "paid_at": null
}
Показывайте payment.amount до последнего знака. Оплата опознаётся по точной сумме: к сумме счёта могут добавляться последние знаки — тысячные у USDT, GRAM и TRX, десятитысячные у SOL, стотысячные у BNB, миллионные у ETH. Перевод ровно этой суммы до expires_at (и ещё пять минут на последний блок) и есть оплата. Адрес одноразовый: после оплаты, отмены или истечения переводы на него автоматически не засчитываются — по такому переводу обратитесь в поддержку. Для повторяющихся пополнений есть адреса клиентов.
  • payment.status: waiting → confirming (перевод найден, идут подтверждения) → paid; вместе со счётом — expired и cancelled. Об оплате приходит обычный invoice.paid с payment.tx_hash.
  • Найденный перевод счёт не теряет. Если перевод пришёл до expires_at, счёт не истекает по часам: он остаётся active с payment.status: confirming, пока перевод обрабатывается, и закрывается invoice.paid. Обычно это минуты, но бывает и дольше — не отменяйте заказ по expires_at, пока статус confirming, ждите вебхука. Дольше всего — в сети Ethereum: USDT обычно 3–25 минут, монета ETH — 15–25 минут. Перевод, ушедший до expires_at, оплачивает счёт, даже если зачислен позже.
  • Недоплата — перевод меньше суммы — счёт не оплачивает. Пришедшее за вычетом платы за обработку ложится на баланс мерчанта, приходит invoice.underpaid с underpayment (received, credited, cost — плата за обработку, asset, network, tx_hash), счёт остаётся открытым с теми же реквизитами. Переплата счёт оплачивает, излишек остаётся у Laveshka.
  • Единицы. amount_units — та же сумма в наименьших единицах учёта: шесть знаков у USDT и TRX, девять у GRAM, SOL, BNB и ETH. У USDT в Ethereum amount_units — ровно единицы контракта (6 знаков): "18003000" и есть сумма перевода. У USDT в BSC контракт считает 18 знаков — если ваш код сам собирает перевод, это amount_units × 10¹². У BNB это гвей (10⁻⁹ BNB), а не wei сети: "10000000" — это 0.01 BNB; перевод в wei — amount_units × 10⁹. У ETH так же — гвей (10⁻⁹ ETH): "2000000" — это 0.002 ETH.
  • Монета метода. Переводить нужно именно её: BNB на реквизиты счёта в USDT (и USDT — на реквизиты в BNB) счёт не оплачивает. BNB в asset — монета, BNB в network — другое имя сети BSC, так что {"asset": "USDT", "network": "BNB"} — это USDT BEP-20; нативная монета пишется {"asset": "BNB", "network": "BSC"}. ETH в network — сеть Ethereum, ETH в asset — монета ETH: ETH на реквизиты счёта в USDT ERC-20 (и USDT — на реквизиты в ETH) счёт не оплачивает.
  • Сумма в GRAM, TRX, SOL, BNB или ETH считается по курсу в момент создания и держится весь срок счёта; зачисление — в той же монете. Комиссия — как у перевода по сети: USDT 0.1 %, GRAM, TRX, SOL, BNB и ETH 0.05 %.
  • Минимальный перевод. У монеты в каждой сети есть наименьший перевод, который сеть зачтёт. Счёт, сумма которого в монете метода меньше него, не выдаётся: 422 validation_error с details.field: amount и details.minimum — минимумом в монете метода. Мелкий счёт выставляйте в USDT по сети, где он проходит, или обычным счётом со ссылкой — там его можно оплатить и с баланса Laveshka.
  • Тот же order_id с тем же методом возвращает тот же счёт и те же реквизиты; с другим методом — 409 duplicate_operation.
  • С баланса Laveshka и через кошельки такой счёт не оплачивается; возврат по API недоступен — плательщик перевода анонимен.
  • Оплата по этой сети или этой монетой временно недоступна — 503 provider_not_configured; реквизиты не выданы — 502 provider_error. В обоих случаях счёт не создаётся.

Экран оплаты

pay_url открывает мини-приложение на экране оплаты: аватар и название мерчанта, описание, сумма, срок действия. Покупатель выбирает, чем платить — балансом Laveshka, переводом по сети, через CryptoBot или xRocket, — и платит одним нажатием (под PIN, если он включён). После оплаты — кнопка «Вернуться в <мерчант>»: ведёт в return_url счёта или в ссылку проекта.

Если счёт истёк, отменён, уже оплачен или принадлежит мерчанту самого пользователя — кнопка оплаты недоступна, причина написана на экране.

Адреса клиентов

Постоянный адрес для вашего клиента — по одному на клиента в каждой сети, навсегда. Клиент пополняет его когда угодно, каждый перевод — депозит на баланс мерчанта с вебхуком. База клиентов у каждого мерчанта своя: клиент 1 у вас и клиент 1 у другого мерчанта получают разные адреса.

POST/addresses

Выдать адрес. Первый вызов выдаёт адрес (created: true), каждый следующий для того же клиента и сети отдаёт его же (created: false).

json
{ "client_id": "1", "network": "BSC", "asset": "USDT" }
200 OK
{
  "id": "addr_17",
  "client_id": "1",
  "network": "BSC",
  "address": "0x8c1d…",
  "assets": ["USDT", "BNB"],
  "min_deposit": { "USDT": "1", "BNB": "0.001666667" },
  "deposits_count": 0,
  "last_deposit_at": null,
  "created_at": "2026-10-06T10:00:00+00:00",
  "created": true
}
ПолеТипЧто это
client_id обяз.до 64ваш id клиента
network обяз.строкаTRX, BSC, ETH, GRAM или SOL (читаются и TRC20/TRON, BEP20/BNB, ERC20/ERC-20/ETHEREUM, TON)
assetUSDTкакую монету ждёте (USDT, GRAM, TRX, SOL, BNB, ETH) — только проверяется, что сеть её принимает

Один адрес принимает все монеты своей сети, они перечислены в assets: на TRON — USDT и TRX, на GRAM — USDT и GRAM, на Solana — USDT и SOL, на BSC — USDT и BNB, на Ethereum — USDT и ETH. Запрос с "asset": "BNB" отдаёт тот же адрес, что и с "asset": "USDT". Проверяйте asset депозита: TRX на TRON-адрес зачисляется как TRX, BNB на BSC-адрес — как BNB, ETH на адрес сети ETH — как ETH. Первый перевод USDT на свежий TRON-адрес стоит отправителю дороже — слот USDT у адреса ещё пуст; на новый адрес Solana первый USDT стоит плательщику ~0.002 SOL — кошелёк создаёт счёт токена.

Адрес клиента в Ethereum и BSC один. Запрос с "network": "ETH" отдаёт клиенту тот же 0x…, что и с "network": "BSC", но это два адреса API — у каждой сети свой addr_… и свои депозиты. В какой сети пришёл перевод, говорит network депозита: USDT BEP-20 придёт с BSC, USDT ERC-20 и монета ETH — с ETH. Сеть следит за адресом только с тех пор, как он выдан в ней: прежде чем называть клиенту адрес для Ethereum, получите его запросом с "network": "ETH".

Ошибки: 422 — сеть или монета; 403 forbidden — мерчант на паузе или клиент не прошёл AML (details.reason: aml); 409 limit_exceeded — потолок адресов; 429 — слишком много новых адресов в минуту; 503 — выдача выключена или сеть недоступна. Мерчанта с адресами клиентов удалить нельзя — деньги на них могут прийти в любой день; его ставят на паузу: новые адреса не выдаются, выданные зачисляют как прежде.

Адреса и депозиты

GET/addresses

Список адресов: client_id, network, limit, before.

GET/addresses/{id}

Один адрес по addr_….

GET/deposits

Депозиты: address_id, client_id, status, limit, before.

GET/deposits/{id}

Один депозит по dep_….

Объект депозита

200 OK
{
  "id": "dep_901",
  "status": "completed",
  "address_id": "addr_17",
  "client_id": "1",
  "address": "0x8c1d…",
  "network": "BSC",
  "asset": "USDT",
  "amount": "25.5",
  "fee": "0.03",
  "net": "25.47",
  "tx_hash": "0x5b…",
  "from_address": "0x9f…",
  "confirmations": 0,
  "confirmations_required": 0,
  "error": null,
  "created_at": "2026-10-06T10:05:00+00:00",
  "completed_at": "2026-10-06T10:05:31+00:00"
}

Статусы: pending (увидели, ждём сеть) → processing (подтверждён, обрабатывается; иногда дольше обычного) → completed (на балансе мерчанта) или failed (не зачислим; причина — в error). Вебхуки: deposit.pending при первом обнаружении, deposit.completed, deposit.failed. Зачислять своему клиенту — только по deposit.completed.

Зачисление — в монете депозита, комиссия как у перевода по сети: USDT 0.1 % (до цента вверх), GRAM, TRX, SOL, BNB и ETH 0.05 %. Депозит ниже min_deposit зачисляется за вычетом платы за обработку, а если она больше суммы — failed. Пыль — рассылки на суммы меньше пары центов — не записывается.

error есть только у failed и бывает одной из фраз: «Не прошёл AML», «Сумма ниже минимальной: плата за обработку больше самого перевода», «Сумма ниже минимальной для зачисления», «Сумма слишком мала для зачисления», «Блок не закрепился в сети» (редкость: в Ethereum USDT виден уже с первого блока — deposit.pending, — а блок ещё может выпасть из цепи) или общая «Депозит не зачислен».

Выплаты с баланса мерчанта

Как это устроено

Всё, что ваш сервер тратит, уходит только с баланса мерчанта: перевод пользователю Laveshka, чек по ссылке, оплата QR СБП, вывод на карту, Steam, Stars и Premium, вывод в блокчейн. Эндпоинты с меткой Выплаты по API работают, только когда владелец включил переключатель Настройки → Выплаты по API (под PIN); иначе — 403 forbidden. Состояние видно в payouts_enabled ответа GET /me.

Выключено по умолчанию — и не зря. Утёкший ключ не дотянется до основного баланса владельца, но может израсходовать всё, что мерчант успел принять, — а рубли, ушедшие по QR или на чужую карту, и монеты, ушедшие на чужой адрес в сети, не вернуть. Включайте, только если сервер действительно платит людям — партнёрки, бонусы, кешбэк.
  • Одна команда. Вывод на карту, Steam, Stars и Premium, вывод в блокчейн — цена по курсу этой минуты, удержание с баланса и отправка за один вызов, без подтверждения. Оплата QR — два шага: проверка называет получателя и цену, оплата её тратит.
  • Страховка от курса. max_debit — «списать не больше»: если цена выше, 409 rate_changed и ничего не списано.
  • Исход — вебхуком. Обычно ответ — processing: деньги удержаны, исполнение идёт; итог приходит вебхуком *.completed / *.failed, а data в нём — тот же объект, что отдаёт GET. На pending и processing вебхуков нет.
  • Любой возврат — на баланс мерчанта. Отказ банка, сети или поставщика после списания возвращает деньги ровно туда, откуда взято удержание; объект становится failed с причиной в error. На основной баланс владельца деньги отсюда не уходят, пушей о тратах по API он не получает.
  • idempotency_key обязателен у каждой траты, и объект читается по нему: GET …/{ваш ключ} — см. идемпотентность.

Перевод пользователю

Выплатить можно двумя способами, и выбор между ними — про то, кто должен существовать. Перевод уходит на конкретный аккаунт Laveshka: если человек его ещё не открывал, платить некому. Чек не адресован никому — деньги ждут на ссылке, получатель открывает её, и аккаунт создаётся по дороге. Платите своим клиентам, которых не знаете заранее, — берите чек.

POST/transfers Выплаты по API

Перевести пользователю Laveshka с баланса мерчанта.

bash
curl -X POST "https://<домен>/merchant/v1/transfers" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "@durov",
    "amount": "5.00",
    "comment": "Бонус за отзыв",
    "idempotency_key": "bonus-99-2026-08-22"
  }'
Python
transfer = await lp.transfers.create(
    "@durov", "5.00",
    comment="Бонус за отзыв",
    idempotency_key="bonus-99-2026-08-22",
)
200 OK
{
  "id": "tr_8f618f50-…",
  "amount": "5.00",
  "currency": "USD",
  "recipient": { "telegram_id": 123456789, "username": "durov" },
  "comment": "Бонус за отзыв",
  "created_at": "2026-08-22T09:12:00+00:00"
}
ПолеТипЧто это
recipient обяз.строка@username, username или Telegram ID пользователя Laveshka. Аккаунт должен уже существовать, иначе 404 not_found
amount обяз.USDбез комиссии; минимум 0.10
commentдо 140покажется получателю
idempotency_key обяз.до 64повтор с тем же ключом вернёт ту же операцию и не спишет второй раз

Выплата чеком

Отложить деньги на ссылку. Списывается сразу вся сумма (amount × activations) и держится в эскроу; каждая активация выплачивает одну долю, невостребованное возвращается при отзыве. Получателя называть не нужно — кто первым откроет ссылку, тот и заберёт.

bash
curl -X POST "https://<домен>/merchant/v1/cheques" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "5.00", "comment": "Выплата №4665", "idempotency_key": "payout-4665"}'
Python
cheque = await lp.cheques.create(
    "5.00", comment="Выплата №4665", idempotency_key="payout-4665",
)
print(cheque.url)
200 OK
{
  "id": "chq_311",
  "public_id": "CQF5L6J29VI",
  "status": "active",
  "amount": "5.00",
  "total": "5.00",
  "currency": "USD",
  "activations_total": 1,
  "activations_used": 0,
  "comment": "Выплата №4665",
  "recipient": "",
  "url": "https://t.me/laveshka?startapp=chq_…",
  "idempotency_key": "payout-4665",
  "created_at": "2026-08-30T18:47:04+00:00",
  "closed_at": null
}
ПолеТипЧто это
amount обяз.строкасколько получает одна активация — не сумма чека; без комиссии; 0.10 … 100 000 $ в asset по курсу
assetUSDTUSDT, GRAM, TRX, SOL, BNB или ETH: списывается с баланса мерчанта в этой монете, получатель получает её же
activations1 … 100 000сколько человек могут забрать по доле; итог amount × activations ≤ 1 000 000 $
commentдо 140покажется получателю
recipient@usernameзабрать сможет только он; пусто — чек на предъявителя
idempotency_key обяз.до 64повтор с тем же ключом вернёт тот же чек, а не выпишет второй. По нему же чек потом читается
url — это и есть деньги. Ссылку достаточно передать получателю, больше ничего не нужно. И наоборот: у кого она есть, тот и заберёт, так что в открытый чат её лучше не класть — либо укажите recipient.

Статус и отзыв чека

GET/cheques/{id}

Один чек — по chq_… или по вашему idempotency_key (удобно, если ответ на создание потерялся). Забрал ли получатель деньги, показывает activations_used: 0 у чека active — ещё не забрал. Статусы: active, completed (все доли разобраны), cancelled; closed_at — когда закрыт.

bash
curl "https://<домен>/merchant/v1/cheques/payout-4665" \
  -H "X-Api-Key: $LAVE_KEY"
Python
cheque = await lp.cheques.get("payout-4665")   # chq_311 или ваш ключ
if cheque.activations_used:
    ...
GET/cheques

Список: status (active|completed|cancelled), limit, before.

POST/cheques/{id}/cancel

Отозвать чек. Невостребованные активации возвращаются на баланс мерчанта, ссылка перестаёт работать. Уже активированные доли вернуть нельзя — это потраченные деньги, как и любая выплата. Только для active.

Рубли: QR и карты

Оплата QR

Ваш сервер оплачивает QR-коды СБП по ссылке из QR: USDT списываются с баланса мерчанта, рубли приходят получателю. Цена — как в приложении Laveshka, все комиссии внутри; единственный курс в ответах — «₽ за 1 USDT». Два шага: проверка называет получателя и сколько USDT спишется и держит эту цену несколько секунд, оплата её тратит.

GET/qr/rate

Ориентир: rate — ₽ за 1 USDT для оплат до small_max_rub, rate_large — для оплат дороже, лимиты min_rub/max_rub, quote_ttl — сколько секунд держится проверенная цена, и max_affordable_rub — сколько рублей покрывает баланс мерчанта. Точную цену конкретного QR называет только проверка.

Проверить QR

POST/qr/check Выплаты по API

Проверить QR и заморозить его цену на несколько секунд. qr — ссылка из QR-кода (https://qr.nspk.ru/…) или текст, в котором она есть. Ничего не списывается.

bash
curl -X POST "https://<домен>/merchant/v1/qr/check" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"qr": "https://qr.nspk.ru/AD10006M…?type=02&bank=100000000111&sum=150000&cur=RUB&crc=1A2B"}'
Python
quote = await lp.qr.check("https://qr.nspk.ru/AD10006M…")
print(quote.payee, quote.amount_rub, quote.debit)
200 OK
{
  "quote_id": "qrq_5f0c2a8e-…",
  "payee": "ООО «Ромашка»",
  "amount_rub": "1500",
  "debit": "18.35",
  "currency": "USDT",
  "rate": "81.74",
  "expires_at": "2026-10-02T20:31:26+00:00"
}

debit — сколько USDT спишется с баланса мерчанта, amount_rub — сколько получит получатель (сумма зашита в самом QR). Одна оплата — 1 … 400 000 ₽. Если USDT на балансе мерчанта не хватает на эту цену, проверка отвечает 409 insufficient_funds («Недостаточно USDT на балансе мерчанта») — цены нет, ничего не списано; пополните баланс и проверьте QR снова. Оплата QR, временно недоступная, — 409 payout_unavailable.

Оплатить QR

POST/qr/payments Выплаты по API

Оплатить проверенный QR с баланса мерчанта.

bash
curl -X POST "https://<домен>/merchant/v1/qr/payments" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"quote_id": "qrq_5f0c2a8e-…", "idempotency_key": "order-4665"}'
Python
payment = await lp.qr.pay(quote.quote_id, idempotency_key="order-4665")

# или проверка и оплата одним вызовом
payment = await lp.qr.pay_link("https://qr.nspk.ru/AD10006M…", idempotency_key="order-4665")
200 OK
{
  "id": "qr_9b1e44d0-…",
  "status": "completed",
  "payee": "ООО «Ромашка»",
  "amount_rub": "1500",
  "debit": "18.35",
  "currency": "USDT",
  "rate": "81.74",
  "error": null,
  "returned": null,
  "returned_at": null,
  "idempotency_key": "order-4665",
  "replayed": false,
  "created_at": "2026-10-02T20:31:05+00:00",
  "completed_at": "2026-10-02T20:31:07+00:00"
}
ПолеТипЧто это
quote_id обяз.строкаиз ответа POST /qr/check
idempotency_key обяз.до 64повтор с тем же ключом вернёт ту же оплату (replayed: true) и второй раз не заплатит; тот же ключ с другим QR — 409 idempotency_conflict

Ответ приходит в течение ~3 секунд: completed, failed или processing — оплата ещё проводится, исход придёт вебхуком qr_payment.completed / qr_payment.failed (или дождитесь его запросом статуса с ?wait=). Если цена истекла не больше минуты назад, QR проверяется заново, и оплата проходит, только если не стала дороже; иначе — 409 quote_expired, а новая цена в error.details.quote.

Магазин вернул покупку — статус остаётся completed, на баланс мерчанта возвращается сумма покупки (returned), комиссия сервиса не возвращается. У оплаты появляются returned и returned_at, приходит qr_payment.reversed.

Статус оплаты QR

GET/qr/payments/{id}

Одна оплата — по qr_… или по вашему idempotency_key. ?wait=N&known=<status> держит ответ, пока статус не уйдёт из known, но не дольше N секунд (≤ 25).

bash
# ждать исхода, пока оплата в работе
curl "https://<домен>/merchant/v1/qr/payments/order-4665?wait=25&known=processing" \
  -H "X-Api-Key: $LAVE_KEY"
Python
payment = await lp.qr.get("order-4665", wait=25, known="processing")
GET/qr/payments

Список: status (pending|processing|completed|failed), limit, before.

Вывод на карту

Рубли на банковскую карту с USDT-баланса мерчанта — одной командой, без подтверждения: цена по курсу этой минуты, холд и отправка за один вызов. Если мерчанту назначена персональная комиссия на вывод на карту, с ней уже считаются GET /card/rate, его price и сама выплата.

GET/card/rate

Курс на 10 000 ₽ (rate), примеры цен (examples), лимиты и max_affordable_rub — сколько рублей покрывает баланс мерчанта. С ?amount_rub=5000 или ?amount_usdt=50 ответ добавляет price — точную цену этой суммы прямо сейчас (не замораживается).

200 OK — GET /card/rate?amount_rub=5000
{
  "available": true,
  "reason": null,
  "currency": "USDT",
  "rate": "83.57",
  "examples": [{ "amount_rub": "1000", "debit": "12.12", "rate": "82.50" }],
  "min_rub": "100",
  "max_rub": "294000",
  "max_affordable_rub": "41850",
  "price": { "amount_rub": "5000", "debit": "59.83", "rate": "83.57" }
}

Вывести на карту

POST/card/payouts Выплаты по API
bash
curl -X POST "https://<домен>/merchant/v1/card/payouts" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"card_number": "2200 7034 5678 9012", "amount_rub": "5000", "idempotency_key": "payout-4665"}'
Python
payout = await lp.card.payout(
    "2200 7034 5678 9012", amount_rub="5000",
    max_debit="61", idempotency_key="payout-4665",
)
200 OK
{
  "id": "cp_7d2a90b1-…",
  "status": "processing",
  "amount_rub": "5000",
  "debit": "59.83",
  "currency": "USDT",
  "rate": "83.57",
  "card": { "brand": "mir", "last4": "9012", "masked": "2200 70•• •••• 9012" },
  "error": null,
  "idempotency_key": "payout-4665",
  "replayed": false,
  "created_at": "2026-10-02T20:40:11+00:00",
  "completed_at": null
}
ПолеТипЧто это
card_number обяз.строканомер карты; пробелы и дефисы допустимы
amount_rubцелые ₽100 … 300 000 на карту. Или amount_usdt
amount_usdtUSDT«потратить не больше»: уйдут наибольшие целые рубли, которые укладываются в эти USDT
max_debitUSDTстраховка — и с amount_rub, и с amount_usdt: если списание выше, 409 rate_changed, ничего не списано
idempotency_key обяз.до 64повтор вернёт ту же выплату и цену заново не считает; тот же ключ с другой картой или суммой — 409 idempotency_conflict

Обычно ответ — processing: рубли ушли в банк, исход придёт вебхуком card_payout.completed или card_payout.failed. Если курс изменился между ценой и отправкой, цена один раз пересчитывается сама. Бывает и pending — USDT удержаны, рубли ещё не ушли в банк; исход придёт тем же вебхуком.

Номер карты не возвращается никогда — ни в ответах, ни в вебхуках, только маска.

Статус выплаты

GET/card/payouts/{id}

Одна выплата — по cp_… или по вашему idempotency_key.

GET/card/payouts

Список: status (pending|processing|completed|failed), limit, before.

Вывод по СБП

Рубли по номеру телефона в любой банк СБП с USDT-баланса мерчанта — так же, как на карту: одной командой, без подтверждения, по курсу этой минуты. Получателя задают три поля: телефон, банк и имя в виде «Имя Ф» — имя и первая буква фамилии, как их видит банк получателя.

GET/sbp/banks

Банки, на которые можно перевести. bank_id передаётся в выплату как есть. Список меняется редко — держите его у себя до часа.

200 OK — GET /sbp/banks
{
  "items": [
    { "bank_id": "100000000111", "name": "Сбербанк" },
    { "bank_id": "100000000004", "name": "Т-Банк" },
    …
  ]
}
GET/sbp/rate

Как GET /card/rate: курс на 10 000 ₽ (rate), примеры цен, лимиты (500 … 100 000 ₽) и max_affordable_rub. С ?amount_rub=5000 или ?amount_usdt=50 — ещё price, точная цена этой суммы прямо сейчас.

Вывести по СБП

POST/sbp/payouts Выплаты по API
bash
curl -X POST "https://<домен>/merchant/v1/sbp/payouts" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone": "+79991234567", "bank_id": "100000000004", "recipient_name": "Дмитрий Р", "amount_rub": "5000", "idempotency_key": "sbp-4665"}'
200 OK
{
  "id": "sbp_3f61c0d2-…",
  "status": "processing",
  "amount_rub": "5000",
  "debit": "59.94",
  "currency": "USDT",
  "rate": "83.41",
  "recipient": {
    "phone": "+7 999 •••-45-67", "last4": "4567",
    "bank_id": "100000000004", "bank_name": "Т-Банк", "name": "Дмитрий Р"
  },
  "error": null,
  "idempotency_key": "sbp-4665",
  "replayed": false,
  "created_at": "2026-10-16T18:40:11+00:00",
  "completed_at": null
}
ПолеТипЧто это
phone обяз.строкамобильный номер РФ: +79991234567, 89991234567 или 9991234567; пробелы, скобки и дефисы допустимы
bank_id обяз.строкабанк получателя из GET /sbp/banks
recipient_name обяз.строкаимя и первая буква фамилии: Дмитрий Р. Фамилия целиком, отчество или латиница — 422 validation_error с подсказкой
amount_rubцелые ₽500 … 100 000 получателю. Или amount_usdt
amount_usdtUSDT«потратить не больше»: уйдут наибольшие целые рубли, которые укладываются в эти USDT
max_debitUSDTстраховка — и с amount_rub, и с amount_usdt: если списание выше, 409 rate_changed, ничего не списано
idempotency_key обяз.до 64повтор вернёт тот же перевод и цену заново не считает; тот же ключ с другим получателем или суммой — 409 idempotency_conflict, как и ключ, которым уже выводили на карту

Обычно ответ — processing: перевод ушёл в банк, исход придёт вебхуком sbp_payout.completed или sbp_payout.failed. Если банк получателя не принял перевод — номер не привязан к этому банку или имя не совпало — перевод failed, USDT вернулись на баланс мерчанта. Если курс изменился между ценой и отправкой, цена один раз пересчитывается сама.

Номер телефона не возвращается никогда — ни в ответах, ни в вебхуках, только маска.

Статус перевода

GET/sbp/payouts/{id}

Один перевод — по sbp_… или по вашему idempotency_key.

GET/sbp/payouts

Список: status (pending|processing|completed|failed), limit, before.

Steam, Stars и Premium

Пополнение Steam

Пополнить кошелёк Steam по логину с USDT-баланса мерчанта — одной командой, как вывод на карту. Точная цена называется в момент заказа; любой отказ возвращает списанное на баланс мерчанта.

GET/steam/rate

min_usd/max_usd одного заказа, currencies — валюты Steam с курсом к доллару, price_factor — ожидаемые USDT за 1 $ пополнения и max_affordable_usd — сколько долларов покрывает баланс мерчанта. С ?amount=1000&steam_currency=RUB ответ добавляет estimate — доллары пополнения и ожидаемое списание.

POST/steam/orders Выплаты по API
bash
curl -X POST "https://<домен>/merchant/v1/steam/orders" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"account": "gamer123", "amount": "1000", "steam_currency": "RUB", "max_debit": "12.50", "idempotency_key": "steam-4665"}'
Python
order = await lp.steam.order(
    "gamer123", amount="1000", steam_currency="RUB",
    max_debit="12.50", idempotency_key="steam-4665",
)
200 OK
{
  "id": "st_3a9c51e2-…",
  "status": "processing",
  "account": "gamer123",
  "amount_usd": "11.90",
  "steam_currency": "RUB",
  "amount_local": "1000.00",
  "debit": "12.32",
  "currency": "USDT",
  "waiting": false,
  "error": null,
  "idempotency_key": "steam-4665",
  "replayed": false,
  "created_at": "2026-10-02T21:05:11+00:00",
  "completed_at": null
}
ПолеТипЧто это
account обяз.строкалогин Steam — не ник профиля; неподходящий логин — 422 steam_account_rejected, ничего не списано
amountстрокасумма в валюте аккаунта (steam_currency, по умолчанию USD); в доллары — по курсу Steam вверх до цента. Или amount_usdt
amount_usdtUSDT«потратить не больше»: наибольшие доллары, которые укладываются в эти USDT
max_debitUSDTстраховка: если цена выше, 409 rate_changed, ничего не списано
idempotency_key обяз.до 64повтор вернёт тот же заказ; тот же ключ с другим логином или суммой — 409 idempotency_conflict

Ответ — в течение ~25 секунд: completed, failed или processing (исход придёт вебхуком steam_order.*). waiting: true — заказ идёт дольше обычного, но в пути. Один заказ — 1 … 1000 $.

Статус заказа Steam

GET/steam/orders/{id}

Один заказ — по st_… или по вашему idempotency_key.

GET/steam/orders

Список: status, limit, before.

Stars и Premium

Telegram Stars или подарочный Premium на @юзернейм с USDT-баланса мерчанта — одной командой. Сколько назвал курс, столько и спишется.

GET/telegram/rate

stars: price_per_unit, min_quantity/max_quantity, presets; premium: options на 3, 6 и 12 месяцев; max_purchase — потолок одной покупки. С ?service=stars&quantity=500 ответ добавляет price — списание за эту покупку.

POST/telegram/orders Выплаты по API
bash
curl -X POST "https://<домен>/merchant/v1/telegram/orders" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"service": "stars", "recipient": "@durov", "quantity": 500, "idempotency_key": "stars-4665"}'
Python
order = await lp.telegram.order(
    "stars", "@durov", 500, idempotency_key="stars-4665",
)
200 OK
{
  "id": "tg_0e7b33c4-…",
  "status": "completed",
  "service": "stars",
  "recipient": "durov",
  "quantity": 500,
  "debit": "8.03",
  "currency": "USDT",
  "refunded": false,
  "error": null,
  "idempotency_key": "stars-4665",
  "replayed": false,
  "created_at": "2026-10-02T21:10:02+00:00",
  "completed_at": "2026-10-02T21:10:04+00:00"
}
ПолеТипЧто это
service обяз.строкаstars или premium
recipient обяз.@юзернеймне найден — 404 not_found, ничего не списано
quantity обяз.числозвёзды или месяцы Premium (3, 6, 12)
max_debitUSDTстраховка: цена выше — 409 rate_changed, ничего не списано
idempotency_key обяз.до 64повтор вернёт ту же покупку; тот же ключ с другой покупкой — 409 idempotency_conflict
Fragment отклонил покупку уже после списания — ответ не ошибка, а покупка failed с refunded: true: деньги уже на балансе мерчанта. Fragment не ответил — processing, сверка доведёт покупку сама (вебхук telegram_order.*). Не ответил до списания — 502 provider_error, ничего не списано.

Статус покупки

GET/telegram/orders/{id}

Одна покупка — по tg_… или по вашему idempotency_key.

GET/telegram/orders

Список: status, service (stars|premium), limit, before.

Вывод в блокчейн

Как это устроено

Ваш сервер выводит монеты на адрес в сети тем же выводом, что человек делает из приложения Laveshka: те же сети, комиссии и минимумы. USDT на любой сети списываются с USDT-баланса мерчанта, GRAM, TRX, SOL, BNB и ETH — с баланса в этой монете (balances в GET /me; USDT там — под ключом USD).

  • Одна команда: цена по курсу этой минуты, удержание с баланса мерчанта и отправка в сеть — за один вызов.
  • Сумма — то, что получит адрес, в монете вывода; наша комиссия — сверху, в монете баланса.
  • Только адрес в сети: USDT по TRC-20, BEP-20, ERC-20 (Ethereum), GRAM и Solana, монеты GRAM, TRX и SOL — каждая по своей сети, BNB — по BSC, ETH — по Ethereum; весь список — GET /withdrawals/networks. Выплаты кошельком Telegram и чеком у этого API нет — для них перевод и чек.
  • USDT в Ethereum ("network": "ETH", так же и монета ETH): адрес 0x… выглядит так же, как в BSC и в L2, — убедитесь, что получатель принимает эту монету именно в сети Ethereum. Когда сеть перегружена, вывод может уйти позже — сам, деньги удержаны. Пока он в работе, перевод может быть переотправлен с другой ценой газа, и tx_hash сменится: окончательный хеш — в withdrawal.completed.
Перевод в блокчейне необратим — ушедшее на чужой адрес не вернуть, проверяйте адрес до отправки. Зато любой отказ после списания возвращает всю сумму вместе с комиссией на баланс мерчанта. Исход приходит вебхуком withdrawal.completed / withdrawal.failed.

Сети и комиссии

GET/withdrawals/networks

Куда можно вывести: монета и сеть, наша комиссия, наименьшая сумма и сколько баланс мерчанта выведет прямо сейчас.

200 OK, фрагмент
{
  "available": true,
  "reason": null,
  "items": [
    {
      "asset": "USDT",
      "network": "TRX",
      "name": "Tether USDT (TRC-20)",
      "currency": "USDT",
      "fee": "4",
      "min_amount": "1",
      "max_amount": "1528.2",
      "decimals": 6,
      "requires_memo": false,
      "address_regex": "^T[1-9A-HJ-NP-Za-km-z]{33}$"
    },
    {
      "asset": "BNB",
      "network": "BSC",
      "name": "BNB (BNB Smart Chain)",
      "currency": "BNB",
      "fee": "0.000166667",
      "min_amount": "0.001666667",
      "max_amount": "0.024833333",
      "decimals": 9,
      "requires_memo": false,
      "address_regex": "^0x[0-9a-fA-F]{40}$"
    },
    …
  ]
}
ПолеТипЧто это
currencyстрокабаланс, с которого спишется вывод, и единица fee, min_amount, max_amount: USDT у USDT на любой сети, сама монета у GRAM, TRX, SOL, BNB и ETH
feeстроканаша комиссия за вывод — сверху суммы, в currency. У каждой сети своя и может меняться, у монет к тому же пересчитывается по курсу: берите её отсюда или из котировки, не зашивайте в код
min_amountстроканаименьшая сумма на адрес, в монете по курсу сейчас
max_amountстрокасколько баланс мерчанта выведет прямо сейчас, комиссия уже вычтена; не больше 10 000 $ в монете. Меньше min_amount — выводить нечего
decimalsчислосколько знаков после точки принимает amount: 6 у USDT и TRX, 9 у GRAM, SOL, BNB и ETH (у BNB и ETH это гвей: точнее 0.000000001 монеты вывод не принимает)
requires_memo, address_regexbool, строканужен ли memo и какой формат адреса; адрес проверяется по нему до списания

available: false с причиной в reason — выплаты по API выключены, мерчант на паузе или вывод временно выключен на сервере; список при этом всё равно приходит. Пары, по которым вывод временно закрыт, в списке нет: вывод в них получает 409 payout_unavailable или 422, ничего не списано.

Цена вывода

GET/withdrawals/quote

Цена вывода прямо сейчас — то же списание, что сделал бы POST /withdrawals, но ничего не удерживается и цена не закрепляется. Параметры: asset, network, amount.

bash
curl "https://<домен>/merchant/v1/withdrawals/quote?asset=USDT&network=TRX&amount=25" \
  -H "X-Api-Key: $LAVE_KEY"
200 OK
{
  "asset": "USDT",
  "network": "TRX",
  "amount": "25",
  "fee": "4",
  "debit": "29",
  "currency": "USDT",
  "amount_usd": "25.00",
  "balance": "1532.2",
  "enough": true
}

Сумма проверяется так же, как у вывода: лишние знаки, ниже минимума или выше потолка — 422 (details.minimum, details.maximum). enough — хватает ли balance.

Вывести

POST/withdrawals Выплаты по API

Вывести монеты на адрес в сети с баланса мерчанта — одной командой, без подтверждения.

bash
curl -X POST "https://<домен>/merchant/v1/withdrawals" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "USDT",
    "network": "TRX",
    "address": "TXr4…9mQp",
    "amount": "25",
    "max_debit": "29",
    "idempotency_key": "wd-4665"
  }'

# вывод BNB — то же самое в монете: до девяти знаков в amount,
# удержание и комиссия — с BNB-баланса мерчанта; ETH — так же,
# {"asset": "ETH", "network": "ETH", …} с ETH-баланса
curl -X POST "https://<домен>/merchant/v1/withdrawals" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"asset": "BNB", "network": "BSC", "address": "0x3f5C…9aB1", "amount": "0.02", "max_debit": "0.020166667", "idempotency_key": "wd-4666"}'
200 OK
{
  "id": "wd_48213",
  "status": "processing",
  "asset": "USDT",
  "network": "TRX",
  "address": "TXr4…9mQp",
  "memo": null,
  "amount": "25",
  "fee": "4",
  "debit": "29",
  "currency": "USDT",
  "amount_usd": "25.00",
  "tx_hash": null,
  "tx_url": null,
  "error": null,
  "idempotency_key": "wd-4665",
  "replayed": false,
  "created_at": "2026-10-08T12:00:11+00:00",
  "completed_at": null
}
ПолеТипЧто это
asset обяз.строкамонета пары из GET /withdrawals/networks: USDT, GRAM, TRX, SOL, BNB, ETH
network обяз.строкакод сети оттуда же: TRX, BSC, ETH, GRAM, SOL; читаются и TRC20/TRON, BEP20/BNB, ERC20/ERC-20/ETHEREUM, TON. BNB здесь — сеть BSC; монета BNB пишется в asset: {"asset": "BNB", "network": "BSC"}
address обяз.до 128адрес получателя в этой сети; неверный формат — 422, ничего не списано
memoстрокатолько если у пары requires_memo; у остальных пар не принимается — 422
amount обяз.строкасколько получит адрес, в монете пары; не больше decimals знаков, от min_amount до потолка — иначе 422. Комиссия — сверху
max_debitстрокастраховка: если списание (amount + fee, в currency) выше, 409 rate_changed, ничего не списано
idempotency_key обяз.до 64повтор вернёт тот же вывод и второй раз не спишет; тот же ключ с другой монетой, сетью, адресом, memo или суммой — 409 idempotency_conflict. Не networks, не quote и не вида wd_<число> — так GET /withdrawals/{id} прочитал бы не ваш ключ (422)

amount — сколько получит адрес, fee — наша комиссия, debit — их сумма, списанная с баланса currency; amount_usd — оценка в долларах на момент вывода. Комиссия 4 в примере — условная: актуальная — в fee ответа GET /withdrawals/networks.

statusЧто значит
pendingденьги удержаны, вывод ещё не ушёл в сеть
processingденьги удержаны, транзакция у сети — обычный ответ на создание
completedсеть подтвердила перевод: tx_hash и tx_url (ссылка на обозреватель) заполнены, приходит withdrawal.completed
failedвывод не состоялся, причина в error, вся сумма с комиссией уже на балансе мерчанта, приходит withdrawal.failed. completed_at — время исхода, у failed тоже
errorЧто значит
«Не прошёл AML»вывод не прошёл AML-проверку
«Сеть отказала — деньги вернулись на баланс»сеть не приняла перевод
«Неверный адрес получателя»сеть не приняла адрес — проверьте его; у BNB и ETH так же отвечает адрес контракта, который не принимает монету
«Сумма больше допустимой для одного вывода», «Максимальная сумма вывода — $X»потолок одного вывода по этой сети — уменьшите сумму или разделите вывод
«Вывод … сейчас недоступен — деньги остались на балансе. …»направление временно закрыто; выберите другую сеть из GET /withdrawals/networks
«Мерчант на паузе», «Выплаты по API у мерчанта выключены», «Мерчант удалён»состояние мерчанта изменилось до отправки вывода
«Вывод отклонён», «Вывод не прошёл»отклонён; любая другая причина

Отказ до списания — ошибка ответа: ничего не списано, вывода нет, ключ не израсходован. Это 422 (пара, адрес, сумма), 409 insufficient_funds («Недостаточно USDT на балансе мерчанта», у монет — GRAM, TRX, SOL, BNB, ETH), 409 rate_changed, 409 payout_unavailable, 403 forbidden, 429 и 503 provider_not_configured. Отказ после списания — не ошибка: ответ 200 с выводом failed, деньги уже вернулись, приходит withdrawal.failed.

Повтор с тем же idempotency_key возвращает тот же вывод (replayed: true): сумма сравнивается числом — 25 и 25.00 одно и то же, сеть — кодом (TRC20 и TRX — одно). Повтор узнаётся раньше всех проверок: даже если выплаты по API с тех пор выключили или сети больше нет в списке, ответ — уже сделанный вывод, а не отказ.

Статус и список

GET/withdrawals/{id}

Один вывод — по wd_… или по вашему idempotency_key.

GET/withdrawals

Список: status (pending|processing|completed|failed), limit, before. Выводов — не больше 30 в минуту на мерчанта (429), повтор с тем же ключом не в счёт.

Пополнение

Покупка USDT за рубли

Обратное направление: рубли → USDT на баланс мерчанта. Ваш сервер заводит покупку, отдаёт плательщику ссылку на оплату по СБП, и после оплаты USDT приходят на USDT-баланс мерчанта. Платить может кто угодно — вы сами или ваш клиент, кошелёк Laveshka ему не нужен. Переключатель Выплаты по API не нужен: покупка ничего не тратит.

  • Одна команда: цена по курсу этой минуты, заявка и счёт на оплату за один вызов — ссылка на оплату уже в ответе.
  • Цена — как в приложении Laveshka: все комиссии внутри курса, единственный курс в ответах — «₽ за 1 USDT». Курс закреплён за покупкой: сколько USDT названо, столько и придёт.
  • Оплата — по QR-коду СБП на счёт юридического лица, без переводов частным лицам.
Исход приходит вебхуком (buy_order.completed, buy_order.cancelled), а data в нём — тот же объект, что отдаёт GET. Закрытая покупка ещё может стать completed: если плательщик успел заплатить в последнюю секунду, поздний платёж зачислится, и придёт buy_order.completed.

Курс покупки

GET/buy/rate

rate — ₽ за 1 USDT, min_rub/max_rub одной покупки, payment_minutes — сколько минут на оплату по ссылке, open_orders/max_open_orders — покупок в работе сейчас и сколько можно сразу, blocked_until — до какого момента покупка закрыта. С ?amount_rub=10000 или ?amount_usdt=100 ответ добавляет price — точную цену этой суммы прямо сейчас (не замораживается).

200 OK — GET /buy/rate?amount_rub=10000
{
  "available": true,
  "reason": null,
  "currency": "USDT",
  "rate": "100.55",
  "min_rub": "500",
  "max_rub": "20000",
  "payment_minutes": 15,
  "open_orders": 0,
  "max_open_orders": 10,
  "blocked_until": null,
  "price": { "amount_rub": "10000", "amount_usdt": "99.45", "rate": "100.55" }
}

Купить USDT

POST/buy/orders

Купить USDT за рубли на баланс мерчанта — одной командой. Ответ обычно приходит за пару секунд; иногда ссылку приходится подождать до 10 секунд — таймаут запроса ставьте не меньше 30 секунд.

bash
curl -X POST "https://<домен>/merchant/v1/buy/orders" \
  -H "X-Api-Key: $LAVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_rub": "10000", "max_rate": "102", "idempotency_key": "topup-4665"}'
Python
order = await lp.buy.order(
    amount_rub="10000", max_rate="102", idempotency_key="topup-4665",
)
print(order.payment_url)

# дождаться исхода опросом, если вебхука нет
order = await lp.buy.wait(order.id, timeout=900)
200 OK
{
  "id": "buy_2c8e61f0-…",
  "status": "awaiting_payment",
  "amount_rub": "10000",
  "amount_usdt": "99.45",
  "currency": "USDT",
  "rate": "100.55",
  "payment_url": "https://qr.nspk.ru/BS1A004TIU6…",
  "pay_until": "2026-10-07T10:14:41+00:00",
  "cancel_reason": null,
  "can_cancel": true,
  "idempotency_key": "topup-4665",
  "replayed": false,
  "created_at": "2026-10-07T09:59:41+00:00",
  "closed_at": null
}
ПолеТипЧто это
amount_rubцелые ₽к оплате, от min_rub до max_rub из GET /buy/rate. Или amount_usdt
amount_usdtUSDTсколько USDT купить, до двух знаков: рубли округляются вверх, придёт не меньше
max_rate₽ за 1 USDTстраховка: если курс выше, 409 rate_changed, ничего не создано
idempotency_key обяз.до 64повтор с тем же ключом вернёт ту же покупку и второго счёта не откроет; тот же ключ с другой суммой — 409 idempotency_conflict

payment_url отдайте плательщику ссылкой (на телефоне она откроет оплату в его банке) или QR-кодом. Платить — точную сумму одним платежом и до pay_until. Ссылка видна, пока покупка awaiting_payment.

statusЧто значит
pendingссылки пока нет — она готовится, обычно секунды (не дольше ~15 с); повтор запроса с тем же ключом отправит покупку
awaiting_paymentссылка выдана, ждём оплату до pay_until
overdueвремя на оплату вышло, итога ещё нет: по ссылке больше не платят, но платёж последней секунды зачислится
completedоплачено — amount_usdt на балансе мерчанта
cancelledзакрыта без денег; cancel_reason: unpaid — не оплатили вовремя, client — вы отменили, unreachable — счёт не выдан

Статус и отмена

GET/buy/orders/{id}

Одна покупка — по buy_… или по вашему idempotency_key.

GET/buy/orders

Список: status (pending|awaiting_payment|overdue|completed|cancelled), limit, before.

POST/buy/orders/{id}/cancel

Снять неоплаченную покупку: счёт снимается, по старой ссылке уже не заплатить. Если оплата пришла в эту же секунду, отмены не будет — покупка зачислится. Пока платёж проверяется, отменить нельзя.

Лимиты мерчанта. В работе — не больше 10 покупок сразу (409 limit_exceeded). Если неоплаченных и снятых покупок слишком много, покупка временно закрывается: 403 buy_blocked до details.until (blocked_until в курсе).

Вебхуки

Настройка

URL задаётся в приложении (Lave Pay → мерчант → Вебхук); там же — секрет подписи whsec_…, кнопка «Тест» и журнал всех доставок. Вебхук — быстрый путь узнать об исходе, но не единственный источник правды: держите рядом опрос GET объекта.

В продакшене — только https и только публичный хост; локальные и приватные адреса отклоняются — и при сохранении, и прямо перед каждой отправкой. Попытка на адрес, который проверка не пропустила, помечается blocked address (приватный или служебный адрес) или invalid response (URL, на который отправить нельзя) и считается обычной неудачной попыткой: она тратит попытку, идёт в здоровье endpoint'а и в суточное автоотключение. Редиректы не выполняются.

События

POST <ваш URL> с Content-Type: application/json:

тело
{
  "id": "evt_3c0d8e7f1f5c4a3e9a6b2b0d0f1e2a3b",
  "type": "invoice.paid",
  "created_at": "2026-08-22T09:15:02+00:00",
  "merchant_id": 12,
  "data": { …объект, как в его GET… }
}
заголовки
X-LavePay-Event-Id:  evt_3c0d8e7f…     # один и тот же при всех повторах
X-LavePay-Event:     invoice.paid
X-LavePay-Attempt:   1                  # номер попытки
X-LavePay-Timestamp: 1787479200         # unix-секунды этой попытки
X-LavePay-Signature: v1=9f2c…           # см. ниже
User-Agent:          LavePay-Webhooks/1.0
typeКогдаdata
invoice.paidпокупатель оплатилсчёт со status: paid, payer, paid_at
invoice.expiredвышел ttl, перевод не найденсчёт
invoice.cancelledвы отменили счётсчёт
invoice.refundedвы сделали возвратсчёт с refunded_at
invoice.underpaidбесшовный счёт оплачен не полностью — пришедшее на балансе мерчантасчёт (active) и underpayment
deposit.pendingперевод на адрес клиента увидендепозит, как в GET /deposits/{id}
deposit.completedдепозит зачислен на баланс мерчантадепозит с fee и net
deposit.failedдепозит не будет зачислендепозит с error
qr_payment.completedоплата QR прошлаоплата, как в GET /qr/payments/{id}
qr_payment.failedбанк не принял оплату — USDT вернулись на баланс мерчантаоплата с error
qr_payment.reversedмагазин вернул прошедшую оплатуоплата с returned, returned_at
card_payout.completedрубли дошли до картывыплата, как в GET /card/payouts/{id}
card_payout.failedвыплата не прошла или отклонена — USDT вернулись на баланс мерчантавыплата с error
sbp_payout.completedрубли дошли до получателя по СБПперевод, как в GET /sbp/payouts/{id}
sbp_payout.failedперевод не прошёл — USDT вернулись на баланс мерчантаперевод с error
steam_order.completedSteam зачислил пополнениезаказ, как в GET /steam/orders/{id}
steam_order.failedSteam не принял заказ — USDT вернулись на баланс мерчантазаказ с error
telegram_order.completedStars или Premium доставленыпокупка, как в GET /telegram/orders/{id}
telegram_order.failedFragment отклонил или не подтвердил покупку — USDT вернулись на баланс мерчантапокупка с error, refunded
withdrawal.completedвывод в блокчейн подтверждён сетьювывод, как в GET /withdrawals/{id}, с tx_hash и tx_url
withdrawal.failedвывод не прошёл (причина — в error) — вся сумма с комиссией вернулась на баланс мерчантавывод с error
buy_order.completedрубли оплачены — USDT на балансе мерчанта (в том числе поздний платёж по закрытой покупке)покупка, как в GET /buy/orders/{id}
buy_order.cancelledпокупка закрыта без денег: не оплачена, снята вами или счёт не выданпокупка с cancel_reason
webhook.testкнопка «Тест» / POST /webhook/test{"message": "…", "merchant_id": 12}

На промежуточные состояния — pending, processing, confirming — вебхуков нет, только на исход.

Проверка подписи

Подпись считается по сырому телу запроса — байты как есть, не пересериализованный JSON:

формула
signature = "v1=" + hex( HMAC_SHA256( secret, timestamp + "." + raw_body ) )

secret — секрет подписи из настроек вебхука (whsec_…). Сравнивайте константным временем и отбрасывайте запросы, чей X-LavePay-Timestamp старше 5 минут — это защита от повторного воспроизведения перехваченного запроса. Дальше — дедупликация по id события: повтор несёт тот же id и то же тело, подпись при этом другая (другой timestamp).

Python · FastAPI + lavepay
from fastapi import FastAPI
from lavepay import LavePay
from lavepay.integrations.fastapi import install

app = FastAPI()
lp = LavePay("LAVE-12-…", webhook_secret="whsec_…")

@lp.invoice_paid()
async def paid(invoice):
    if invoice.matches(order_id=invoice.order_id):
        mark_order_paid(invoice.order_id)   # идемпотентно по заказу

install(app, lp)   # POST /lavepay: подпись, срок и дедупликация — внутри; 200 уходит сразу
Python · FastAPI
import hmac, hashlib, time
from fastapi import FastAPI, Request, HTTPException

SECRET = "whsec_…"
app = FastAPI()

@app.post("/lavepay")
async def lavepay(request: Request):
    body = await request.body()
    ts = request.headers.get("x-lavepay-timestamp", "")
    sig = request.headers.get("x-lavepay-signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        raise HTTPException(400, "stale")
    expected = "v1=" + hmac.new(SECRET.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        raise HTTPException(401, "bad signature")

    event = await request.json()
    if event["type"] == "invoice.paid":
        mark_order_paid(event["data"]["order_id"], event["id"])  # идемпотентно по event id
    return {"ok": True}
Node · Express
import crypto from "node:crypto";
import express from "express";

const SECRET = "whsec_…";
const app = express();

app.post("/lavepay", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.get("x-lavepay-timestamp") ?? "";
  const sig = req.get("x-lavepay-signature") ?? "";
  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
  const expected = "v1=" + crypto.createHmac("sha256", SECRET).update(`${ts}.`).update(req.body).digest("hex");
  if (expected.length !== sig.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  if (event.type === "invoice.paid") markOrderPaid(event.data.order_id, event.id);
  res.sendStatus(200);
});

Ответы и повторы

  • 2xx — событие доставлено; больше не придёт.
  • Любой другой ответ или его отсутствие (таймаут 10 с) — мы повторим.
  • Отвечайте быстро: примите событие, поставьте в очередь, верните 200. Обработку делайте после ответа.
  • Дедуплицируйте по id события (evt_…).
  • Редиректы не выполняются: 3xx считается неудачей.
Ваш ответЧто делаем
2xxдоставлено
нет ответа, таймаут, 408, 425, 5xxповтор по расписанию: через 30 с, 2 мин, 10 мин, 30 мин, 1 ч, 3 ч, 6 ч — всего 8 попыток, ±20 % джиттера
429, 503 с Retry-Afterждём столько, сколько сказано (не меньше расписания, не больше часа)
3xx и прочие 4xxошибка маршрута или авторизации на вашей стороне: 3 попытки (хватает пережить деплой), потом событие помечается failed
410 Goneendpoint отключается немедленно

Здоровье endpoint'а

Видно в приложении и в GET /webhook → state:

  • healthy — последние события доставлены;
  • degraded — были неудачи, повторяем;
  • down — 3+ неудачи подряд. Новые события не летят сразу, а становятся в очередь за уже запланированной попыткой. Первый же 2xx сбрасывает счётчик и отправляет всю очередь разом, по порядку;
  • disabled — 24 часа без единого 2xx (или 410): отправка остановлена, владелец получает уведомление. События за это время сохраняются со статусом failed. Кнопка «Включить и отправить заново» (или смена URL) сбрасывает здоровье и ставит в очередь всё, что накопилось за последние 7 дней.

Смена URL считается новым endpoint'ом: здоровье обнуляется, ожидающие события переезжают на новый адрес.

Вебхук не пришёл? Откройте Вебхук → Журнал отправок в приложении: там каждая попытка с HTTP-кодом, временем ответа и телом. Кнопка «Отправить ещё раз» повторяет событие, «Тест» проверяет маршрут. То же самое — через API вебхуков.

API вебхуков

GET/webhook

Состояние endpoint'а: state (off — URL не задан, healthy, degraded, down, disabled), url, failure_streak — неудач подряд, failing_since, last_success_at, last_failure_at, disabled_at и disabled_reason — почему отправка остановлена: endpoint answered 410 Gone, no successful delivery in 24 hours или switched off.

POST/webhook/test

Отправить webhook.test на ваш URL — подписанный, как любое настоящее событие. Удобно проверить интеграцию до первого счёта.

bash
curl -X POST "https://<домен>/merchant/v1/webhook/test" -H "X-Api-Key: $LAVE_KEY"
Python
ref = await lp.webhooks.test()
health = await lp.webhooks.get()
GET/webhook/deliveries

Журнал отправок: status (pending|delivered|failed), limit, before.

У каждой отправки — число попыток, last_status_code, начало тела ответа (last_response), время ответа и last_error — причина последней неудачи общей категорией: timeout, connection failed, TLS error, invalid response, blocked address, HTTP <код> (у 3xx — HTTP <код>: redirects are not followed) или webhook disabled: <причина> — у событий, отложенных выключенным endpoint'ом. Хостов, адресов и текста ошибок соединения там нет.

POST/webhook/deliveries/{event_id}/retry

Отправить событие ещё раз на текущий URL.

Справочник

История баланса

GET/history

Всё, что двигало баланс мерчанта, новое первым: оплаты и возвраты счетов, депозиты на адреса клиентов, переводы, чеки, оплаты QR, выводы на карту, Steam и Telegram, выводы в блокчейн, покупка USDT, вывод на баланс владельца. Одна строка — одна проводка. В приложении то же самое — на вкладке «История» мерчанта.

bash
curl "https://<домен>/merchant/v1/history?limit=50&before=18234" \
  -H "X-Api-Key: $LAVE_KEY"
Python
page = await lp.history.list(limit=50)
async for item in lp.history.iter():   # все страницы подряд
    print(item.type, item.amount, item.asset)
200 OK
{
  "items": [
    {
      "id": "op_5d0c9a1e-…",
      "type": "buy_order",
      "direction": "in",
      "amount": "6.03",
      "asset": "USDT",
      "balance_after": "123.70",
      "object_id": "buy_2c8e61f0-…",
      "status": "completed",
      "created_at": "2026-10-07T10:24:51+00:00"
    },
    {
      "id": "op_91aa07b3-…",
      "type": "card_payout",
      "direction": "out",
      "amount": "-4.31",
      "asset": "USDT",
      "balance_after": "117.67",
      "object_id": "cp_7f8b89a5-…",
      "status": "completed",
      "created_at": "2026-10-07T09:58:02+00:00"
    }
  ],
  "next_cursor": 18234
}
typeЧто этоobject_id
invoicein — оплата счёта (и недоплата бесшовного счёта), out — возвратinv_…
depositдепозит на постоянный адрес клиентаdep_…
transferперевод пользователюtr_…
chequeout — чек создан, in — невостребованное вернулосьchq_…
qr_paymentout — оплата QR, in — не прошла или магазин вернулqr_…
card_payoutout — вывод на карту, in — не прошёл, деньги вернулисьcp_…
sbp_payoutout — вывод по СБП, in — не прошёл, деньги вернулисьsbp_…
steam_orderout — пополнение Steam, in — возвратst_…
telegram_orderout — Stars или Premium, in — возвратtg_…
withdrawalout — вывод в блокчейн (сумма с комиссией, в монете вывода), in — не прошёл, деньги вернулисьwd_…
buy_orderпокупка USDT за рублиbuy_…
settlementвывод на баланс владельца из приложения—
otherручная корректировка—

amount — со знаком, в единицах asset (стейбл — USDT): сумма строк по активу — его баланс, balance_after — остаток после строки. status — статус объекта сейчас, тот же, что отдаёт его GET: у выплаты на карту, которая не прошла, и списание, и возврат покажут failed, у недоплаты бесшовного счёта — статус самого счёта (active, пока он не оплачен). Пагинация — как везде.

Все эндпоинты

База — https://<домен>/merchant/v1. Выплаты по API — нужен переключатель.

ЭндпоинтЧто делает
GET /meмерчант и его балансы
GET /historyистория баланса
POST /invoicesвыставить счёт; с method — бесшовный
GET /invoicesсписок счетов
GET /invoices/{id}один счёт
POST /invoices/{id}/cancelотменить
POST /invoices/{id}/refundвернуть покупателю
POST /addressesпостоянный адрес клиента
GET /addresses, /addresses/{id}адреса
GET /deposits, /deposits/{id}депозиты на адреса
POST /transfersперевод пользователю Выплаты по API
POST /chequesчек по ссылке Выплаты по API
GET /cheques, /cheques/{id}чеки
POST /cheques/{id}/cancelотозвать чек
GET /qr/rateориентир курса QR
POST /qr/checkпроверить QR, заморозить цену Выплаты по API
POST /qr/paymentsоплатить QR Выплаты по API
GET /qr/payments, /qr/payments/{id}оплаты QR; ?wait= ждёт исхода
GET /card/rateкурс вывода на карту, ?amount_rub= — цена
POST /card/payoutsвывести на карту Выплаты по API
GET /card/payouts, /card/payouts/{id}выплаты на карту
GET /sbp/banksбанки СБП для вывода
GET /sbp/rateкурс вывода по СБП, ?amount_rub= — цена
POST /sbp/payoutsвывести по СБП на телефон Выплаты по API
GET /sbp/payouts, /sbp/payouts/{id}переводы по СБП
GET /steam/rateкурс Steam, ?amount= — оценка
POST /steam/ordersпополнить Steam Выплаты по API
GET /steam/orders, /steam/orders/{id}заказы Steam
GET /telegram/rateкурс Stars и Premium, ?service=&quantity= — цена
POST /telegram/ordersкупить Stars или Premium Выплаты по API
GET /telegram/orders, /telegram/orders/{id}покупки Stars и Premium
GET /withdrawals/networksсети, комиссии, минимумы
GET /withdrawals/quoteцена вывода
POST /withdrawalsвывести в блокчейн Выплаты по API
GET /withdrawals, /withdrawals/{id}выводы
GET /buy/rateкурс покупки USDT, ?amount_rub= — цена
POST /buy/ordersкупить USDT за рубли
GET /buy/orders, /buy/orders/{id}покупки
POST /buy/orders/{id}/cancelснять неоплаченную покупку
GET /webhookздоровье endpoint'а
POST /webhook/testтестовое событие
GET /webhook/deliveriesжурнал отправок
POST /webhook/deliveries/{event_id}/retryотправить событие ещё раз

Изменения

Что появилось в API, по датам выкладки. Старые поля и ответы не меняются — только добавляются.

  • Монета ETH по сети Ethereum: оплата счетов, бесшовные счета, адреса клиентов, вывод в блокчейн и чеки.
  • USDT по сети Ethereum (ERC-20): сеть ETH (и ERC20, ERC-20, ETHEREUM) в оплате счетов, бесшовных счетах, адресах клиентов и выводе в блокчейн; ход подтверждений — confirmations и confirmations_required.
  • Бесшовный счёт меньше минимального перевода в сети метода не выдаётся: 422 validation_error с details.minimum.
  • Нативный BNB по сети BSC: оплата счетов, бесшовные счета, адреса клиентов, вывод в блокчейн.
  • Бесшовный счёт с найденным переводом (payment.status: confirming) не истекает по часам.
  • Вывод в блокчейн с баланса мерчанта: GET /withdrawals/networks, /withdrawals/quote, POST /withdrawals, вебхуки withdrawal.*.
  • Бесшовные счета (method в POST /invoices) и постоянные адреса клиентов: POST /addresses, GET /deposits, вебхуки invoice.underpaid, deposit.*.
  • Покупка USDT за рубли: GET /buy/rate, POST /buy/orders, вебхуки buy_order.*. GET /history — история баланса. Библиотека lavepay на PyPI.
  • Steam, Stars и Premium по API: /steam/*, /telegram/*. Оплата QR СБП и вывод на карту: /qr/*, /card/*.
  • Чек — до 100 000 активаций и до 1 000 000 $ на чек.
  • Чеки: POST /cheques, статус и отзыв; чек читается по своему idempotency_key.
  • Комиссия с оплаты зависит от способа (paid_via); на экране оплаты — CryptoBot и xRocket. Бот стал @laveshka.
  • Первый выпуск: счета, возвраты, переводы, вебхуки с подписью.

Ключ для живых запросов

Запросы из консоли уходят из вашего браузера напрямую в API этого же домена. Ключ не отправляется никуда больше и не сохраняется на сервере.