Базовый URL /v1. Авторизация — заголовок
Authorization: Bearer sk_test_…. Все суммы передаются строками, чтобы не терять точность.
Валюта счёта — одна из: USD, EUR,
RUB, KZT, UAH.
Криптовалюту выбирает покупатель на странице оплаты; их список — в /v1/currencies.
checkout_url.invoice.paid и выдайте товар.Сумма — строкой, в валюте счёта и с её числом знаков: "100.00".
В ответе amount — это фиат, а amount_crypto и
amount_received — монеты выбранного актива. Сравнивать полученное нужно с
amount_crypto: у BTC-счёта на 100 USD amount_received
будет вида "0.0015462746".
| Путь | Право ключа | Назначение | |
|---|---|---|---|
| POST | /v1/invoices | invoices:write | Создать счёт |
| GET | /v1/invoices | invoices:read | Список счетов |
| GET | /v1/invoices/:id | invoices:read | Статус счёта |
| POST | /v1/invoices/:id/cancel | invoices:write | Отменить неоплаченный счёт |
| GET | /v1/rates | invoices:read | Оценить сумму в крипте до создания счёта |
| GET | /v1/currencies | — | Поддерживаемые активы и сети (BTC, ETH, SOL, USDT, USDC) |
| GET | /v1/balances | balances:read | Балансы по активам |
| POST | /v1/payouts | payouts:write | Вывести средства |
| GET | /v1/payouts | payouts:read | История выплат |
| GET | /v1/payout-addresses | payouts:read | Доверенные адреса |
| POST | /v1/payout-addresses | payouts:write | Добавить адрес (период охлаждения) |
| POST | /v1/refunds | payouts:write | Вернуть покупателю |
| GET | /v1/refunds | payouts:read | История возвратов |
| GET | /v1/webhook_endpoints | webhooks:manage | Список эндпоинтов |
| POST | /v1/webhook_endpoints | webhooks:manage | Добавить эндпоинт, вернёт секрет |
| DELETE | /v1/webhook_endpoints/:id | webhooks:manage | Удалить эндпоинт |
| PATCH | /v1/webhook_endpoints/:id | webhooks:manage | Изменить подписку, адрес или паузу |
| GET | /v1/events | events:read | Лента событий — работает и без вебхуков |
Покупатель платит, не уходя со страницы. Счёт создаётся на вашем сервере, в кнопку передаётся его id.
Или программно, чтобы поймать результат:
Виджет не читает вашу страницу и не имеет доступа к её данным;
обмен идёт только через postMessage с проверкой источника.
| Статус | Что значит | Выдавать товар? |
|---|---|---|
| pending | Ждём оплату, валюта ещё не выбрана или платёж не пришёл | нет |
| detected | Транзакция замечена, подтверждений пока нет | нет |
| confirming | Идут подтверждения сети | нет |
| partially_paid | Пришло меньше суммы, срок ещё не истёк | нет |
| paid | Оплачен полностью и подтверждён | да |
| paid_late | Оплачен после истечения, принят по вашей политике | да |
| overpaid | Прислали больше — заказ оплачен, излишек можно вернуть | да |
| underpaid | Недоплата, срок истёк | нет, решайте вручную |
| expired | Срок истёк без оплаты | нет |
| canceled | Отменён вами | нет |
| quarantined | Средства на комплаенс-проверке | нет |
| failed | Техническая ошибка | нет |
POST на ваш URL при каждой смене статуса. Заголовок
gw-signature: t=<unix>,v1=<hmac>, где подпись — HMAC-SHA256 от строки
timestamp.тело секретом эндпоинта.
Проверяйте подпись и свежесть таймстемпа, иначе кто угодно
сможет прислать вам «оплату». Порядок доставки не гарантирован — при сомнении запросите
GET /v1/invoices/:id.
Node.js
PHP
Секрет подставляется в HMAC целиком, вместе с префиксом
whsec_ — ровно так, как он показан при создании эндпоинта.
Python
Не дошло — повторяем 8 раз с нарастающей паузой до 12 часов. История доставок с отправленной подписью, телом запроса и ответом вашего сервера — в кабинете, там же ручной повтор.
Можно подписаться не на всё: передайте events при создании
эндпоинта, и приходить будут только они. Без этого поля приходит всё.
Товар выдавайте по invoice.paid,
а не по payment.detected. Второе означает «перевод виден в сети» — подтверждений может
ещё не хватать, и в поле status внутри такого события лежит текущий статус счёта,
а не смысл самого события. Смысл события — в его type.
Один и тот же URL дважды подключить нельзя: события приходили бы в двух копиях, и заказ, обработанный по доставке, выдался бы дважды. Менять подписку, адрес и паузу существующего эндпоинта можно в кабинете, не пересоздавая его — секрет при этом остаётся прежним.
Если вебхук не дошёл — или вы просто не хотите поднимать приёмник — те же события лежат в ленте. Она не зависит от эндпоинтов: события пишутся, даже если ни один вебхук не подключён.
Событие происходит один раз и имеет один
id, сколько бы эндпоинтов его ни получили — по нему и дедуплицируйте.
after продолжает чтение с места, где вы остановились.
Списки отдаются страницами: limit (1–100, по умолчанию 50) и
offset. В ответе — total и
has_more, так что видно, осталось ли что-то за краем.
Счета фильтруются по status
и по вашему order_id — по нему заказ находится, даже если его
id у вас потерялся.
В других API это называют идемпотентностью. Речь о простой вещи: ваш запрос ушёл, ответ не дошёл, вы повторяете — и не хотите получить второй счёт на тот же заказ.
Один order_id сам по себе этого не даёт — два запроса с одним
order_id создадут два счёта. Уникальность обеспечивает именно заголовок ниже.
Передавайте Idempotency-Key при создании счёта. Повтор с тем же ключом вернёт
исходный счёт, а не создаст второй. Тот же ключ с другим телом — ошибка
409 idempotency_conflict. Ключи хранятся сутки, после чего тот же ключ
снова создаёт новый счёт — берите его от идентификатора заказа, а не от даты.
Секретный ключ можно выпустить с ограниченным набором прав — например, только на чтение,
если вы отдаёте его подрядчику для сверки. Передайте scopes при создании
ключа в кабинете; без него ключ получает всё, что может выдать ваша роль.
Доступны:
invoices:read, invoices:write,
payouts:read,
payouts:write, balances:read,
webhooks:manage, events:read.
Публичный ключ pk_ можно отдавать в браузер: он читает один счёт по его id и больше ничего.
Покупатель прислал не ту сумму — это не тупик, решение за вами.
| Что случилось | Статус | Что можно сделать |
|---|---|---|
| Прислал меньше | underpaid | Принять как оплату (зачислится фактически полученное) или вернуть покупателю |
| Прислал больше | overpaid | Закрыть заказ; излишек остаётся на балансе и возвращается через возврат |
| Не успел | expired | Продлить счёт — курс пересчитается |
| Отправитель под санкциями | quarantined | Средства заморожены до решения комплаенса — товар пока не отгружайте |
Недоплата больше 5% требует подтверждения:
первый запрос вернёт 400 confirm_shortfall с точным размером недоплаты
в error.details.shortfall_bps, повторите с этим значением в поле
confirm_shortfall_bps.
Формат одинаковый для всех ответов:
| Код | HTTP | Что делать |
|---|---|---|
| missing_api_key / invalid_api_key | 401 | Проверьте заголовок и не отозван ли ключ |
| insufficient_scope | 403 | Ключу не хватает права — выпустите с нужным |
| account_suspended | 403 | Аккаунт заблокирован, напишите в поддержку |
| live_not_available | 409 | При выпуске боевого ключа в кабинете: пройдите верификацию |
| unsupported_currency | 400 | Список валют — в /v1/currencies |
| rate_unavailable | 503 | Курс временно недоступен, повторите через минуту |
| address_not_whitelisted | 400 | Добавьте адрес выплаты и дождитесь охлаждения |
| ip_not_allowed | 403 | Ключ привязан к другим адресам — вызывайте с разрешённого сервера |
| rate_limited | 429 | Повторите через Retry-After секунд; сколько осталось — в X-RateLimit-Remaining |
| request_in_progress | 409 | Тот же Idempotency-Key ещё выполняется — повторите через секунду, ответ придёт тот же |
| idempotency_conflict | 409 | Тот же ключ с другим телом — возьмите новый ключ |
| wrong_mode | 403 | Ключ и счёт в разных режимах: боевой счёт — боевым ключом, тестовый — тестовым |
| live_key_required | 403 | Вывод возможен только боевым ключом: тестовые деньги не покидают песочницу |
| step_up_required | 403 | Боевой адрес вывода добавляется в кабинете, где мы просим второй фактор. В самом кабинете тот же код приходит с 401 — там это просьба подтвердить себя, а не запрет |
| method_not_allowed | 405 | Другой метод для этого адреса — подходящие перечислены в заголовке Allow |
| unsupported_media_type | 415 | Отправляйте тело с Content-Type: application/json |
| invalid_body / invalid_json / forbidden_key | 400 | Тело должно быть объектом JSON; __proto__ и подобные поля не принимаются |
| endpoint_exists | 409 | Такой URL уже подключён — иначе события приходили бы дважды |
| amount_too_small | 400 | Сумма меньше минимальной для выбранного актива |
| invalid_amount | 400 | Сумма не число, отрицательна или с большим числом знаков, чем есть у валюты |
| unknown_cursor | 400 | after в ленте событий должен быть id события из этой же ленты |
| public_key_not_allowed | 403 | pk_ читает счёт по id и курс; всё остальное — секретным ключом |
Ключ — это доступ к деньгам. Держите его только на сервере: из браузера или мобильного приложения
его видно любому. При выпуске боевого ключа можно указать IP-адреса вашего сервера — тогда утёкший
ключ бесполезен откуда-либо ещё, а запрос с чужого адреса вернёт ip_not_allowed.
Ключ передаётся заголовком Authorization: Bearer sk_… либо
X-Api-Key: sk_… — работают оба.
Ключ показывается один раз. Потеряли — отзовите и выпустите новый; скомпрометированный ключ отзывайте немедленно, отзыв действует сразу.
Ключи sk_test_… работают сразу после регистрации. Тестовый счёт выставляет
заведомо неоплачиваемый адрес вида sandbox-… — реальные монеты на него отправить нельзя.
Оплату можно сымитировать кнопкой на странице оплаты или запросом:
Ключ обязателен: иначе любой, кому попала ссылка на счёт, мог бы отправить в вашу интеграцию событие об оплате.
Валюту оплаты счёт получает на странице оплаты. Если
вы имитируете платёж запросом, выберите её сами — иначе simulate ответит
no_option:
Что в песочнице работает не так:
/v1/payouts
ответит live_key_required. Отладить можно только обработку ошибок.Тестовые деньги учитываются отдельно и не выводятся.