Интеграция по API

REST API, JSON, HTTPS. Базовый URL:

https://zoltopay.com/gw
Все запросы с примерами — в коллекции Postman.

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

  1. Зарегистрируйтесь — API-ключ (формат gw_…) покажется сразу после регистрации. Потерянный ключ можно перевыпустить в кабинете.
  2. В кабинете → Настройки: укажите адрес выплат, правила выплат и Callback URL, затем нажмите «Создать новый секрет» для подписи вебхуков.
  3. Ваш сервер создаёт инвойс → перенаправляет клиента на pay_url (готовая форма оплаты) → ждёт вебхук invoice.paid → проверяет подпись и статус → выдаёт товар.
Все суммы в запросах и ответах — строки ("25.50"), чтобы не терять точность.

Авторизация

Каждый запрос — с заголовком:

Authorization: Bearer gw_ваш_ключ

Ключ даёт полный доступ к аккаунту, включая выплаты. Используйте его только на сервере, никогда — в браузере или мобильном приложении. При утечке выпустите новый в кабинете.

Сети и монеты

Список доступных монет с минимальным платежом и сбором за вывод — GET /v1/currencies:

{"currencies":[{"currency_id":"USDT.Tron","coin":"USDT","network":"TRON (TRC-20)","min_payment_usd":"10","payout_fee_usd":"2"}]}
currency_idМонетаСетьМин. платёж
USDT.TronUSDTTRON (TRC-20)10 USD
USDT.BNBSmartChainUSDTBNB Chain (BEP-20)10 USD
USDC.BNBSmartChainUSDCBNB Chain (BEP-20)10 USD
USDT.PolygonUSDTPolygon10 USD
USDC.PolygonUSDCPolygon10 USD
USDT.ArbitrumUSDTArbitrum One10 USD
USDC.ArbitrumUSDCArbitrum One10 USD
USDT.EthereumUSDTEthereum (ERC-20)50 USD
USDC.EthereumUSDCEthereum (ERC-20)50 USD

Значение currency_id передаётся при создании инвойса и выплаты. Клиенту обязательно покажите сеть: перевод в другой сети автоматически не зачисляется.

Создание инвойса

POST /v1/invoices
ПолеТипОписание
order_idstringобязательноВаш номер заказа. Уникален в пределах аккаунта.
amountstringобязательноСумма в валюте currency. Минимум 10 USD.
currencystringМонета и сеть, напр. USDT.Tron. По умолчанию — монета выплат из настроек.
ttl_minutesintСрок жизни инвойса, по умолчанию 60.
emailstringEmail клиента (необязательно).
return_urlstringКуда вернуть покупателя со страницы оплаты после оплаты, https://… (необязательно).
curl -X POST https://zoltopay.com/gw/v1/invoices \
  -H "Authorization: Bearer gw_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"ORDER-1001","amount":"25","currency":"USDT.Tron","return_url":"https://shop.example/order/1001"}'

Ответ 201 Created:

{
  "id": 42,
  "order_id": "ORDER-1001",
  "amount": "25",
  "currency": "USDT.Tron",
  "amount_usd": "25.01",
  "status": "NEW",
  "pay_url": "https://zoltopay.com/checkout/42-9f3c1a7be0d24c55e1aa",
  "pay_address": "TXyz...abc",
  "amount_to_pay": "25.01",
  "addresses": [{"address": "TXyz...abc", "currency_id": "USDT.Tron"}],
  "expires_at": "2026-10-20T12:00:00Z",
  "created_at": "2026-10-20T11:00:00Z"
}

Форма оплаты (самый быстрый способ)

В ответе на создание инвойса всегда есть pay_url — ссылка на готовую страницу оплаты Zoltopay. Достаточно перенаправить на неё покупателя:

// после POST /v1/invoices
header('Location: ' . $invoice['pay_url']);   // PHP
res.redirect(invoice.pay_url);                // Node.js / Express

На странице покупатель видит название вашего магазина и номер заказа, точную сумму, монету и сеть, адрес с QR-кодом и кнопками «Копировать», таймер до истечения инвойса. Статус обновляется сам: после поступления платежа страница показывает «Оплата получена» и через 5 секунд возвращает покупателя на return_url (если вы его передали).

  • Ссылку можно открыть в той же вкладке, в новой или во фрейме, отправить письмом или в мессенджер.
  • Страница истёкшего инвойса предупреждает не платить и предлагает вернуться в магазин.
  • Возврат на return_url — только для удобства покупателя. Факт оплаты подтверждайте вебхуком invoice.paid или запросом GET /v1/invoices/{id}, а не по факту возврата.

Своя форма оплаты

Если хотите показывать оплату в своём дизайне — используйте pay_address и amount_to_pay из ответа:

  • покажите клиенту адрес, сеть (из currency) и точную сумму amount_to_pay (всегда 2 знака после запятой; может быть больше суммы заказа на 0,01–0,99) — с копированием в один клик и QR-кодом адреса;
  • предупредите: «переведите ровно эту сумму одним платежом». По сумме платёж находит ваш заказ — если клиент округлит её, платёж уйдёт на ручной разбор;
  • покажите таймер до expires_at.
Один адрес обслуживает много заказов. Не показывайте адрес без суммы и не используйте его повторно для других заказов — для каждого заказа создавайте новый инвойс.

Статус инвойса

GET /v1/invoices/{id}
GET /v1/invoices?limit=50

Возвращает тот же объект, что и при создании, с актуальными status, paid_at. Используйте как основной способ проверки перед выдачей товара и как запасной, если вебхук не дошёл.

Статусы инвойса

СтатусЗначениеЧто делать
NEWОжидает оплатыЖдать
PAIDОплачен (допуск ±1%)Выдать товар
OVERPAIDОплачен с переплатойВыдать товар; излишек на вашем балансе
UNDERPAIDОплачен не полностьюРешить по своей политике (доплата/возврат)
EXPIREDИстёк без оплатыСоздать новый инвойс

Вебхуки

На ваш Callback URL приходит POST с JSON-телом и заголовками:

Content-Type: application/json
X-Event: invoice.paid
X-Signature: 5f2c…  (hex HMAC-SHA256 тела запроса вашим секретом)

Ответьте любым 2xx в течение 15 секунд. Иначе повторим доставку с нарастающей паузой (до 1 часа между попытками, всего 15 попыток). Обработчик должен быть идемпотентным: одно событие может прийти дважды.

СобытиеТело
invoice.paid{"invoice_id":42,"order_id":"ORDER-1001","status":"PAID","paid_usd":"25.01","amount_usd":"25.01"}
status может быть PAID или OVERPAID
invoice.underpaid{"invoice_id":42,"order_id":"ORDER-1001","status":"UNDERPAID","paid_usd":"20","amount_usd":"20"}
payout.confirmed{"payout_id":7,"status":"CONFIRMED","tx_hash":"a1b2…"}
payout.failed{"payout_id":7,"status":"FAILED","reason":"…"}
сумма и сбор возвращены на баланс

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

Считайте HMAC-SHA256 от сырого тела запроса (до разбора JSON) и сравните с X-Signature в постоянном времени.

PHP

$body = file_get_contents('php://input');
$sig  = hash_hmac('sha256', $body, getenv('ZOLTOPAY_WEBHOOK_SECRET'));
if (!hash_equals($sig, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
$event = json_decode($body, true);
// дополнительно: GET /v1/invoices/{invoice_id} и проверить status
http_response_code(200);

Node.js (Express)

app.post('/zoltopay/callback', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = crypto.createHmac('sha256', process.env.ZOLTOPAY_WEBHOOK_SECRET)
                    .update(req.body).digest('hex');
  const got = req.get('X-Signature') || '';
  if (sig.length !== got.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(got)))
    return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);
});

Python

import hmac, hashlib
sig = hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, request.headers.get("X-Signature", "")):
    abort(401)

Баланс

GET /v1/balance
{"balance_usd": "152.37", "currency": "USD", "mode": "DIRECT_PAYOUT"}

Выплаты

Автоматические выплаты настраиваются в кабинете (сразу / по порогу). Ручной вывод или возврат клиенту:

POST /v1/payouts
ПолеТипОписание
request_idstringобязательноВаш уникальный ключ идемпотентности. Повтор с тем же request_id не создаст вторую выплату.
addressstringобязательноАдрес получателя.
amountstringобязательноСумма к получению. С баланса спишется сумма + сетевой сбор. Минимум 20 USD.
currencystringПо умолчанию — монета выплат из настроек.
curl -X POST https://zoltopay.com/gw/v1/payouts \
  -H "Authorization: Bearer gw_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"refund-ORDER-1001","address":"TAbc...","amount":"25","currency":"USDT.Tron"}'

Ответ 202 Accepted (или 200 при повторе того же request_id):

{"id":7,"request_id":"refund-ORDER-1001","address":"TAbc...","amount":"25","currency":"USDT.Tron","status":"SENT"}

Статусы выплаты: QUEUED → SENT → CONFIRMED, либо FAILED (сумма возвращена на баланс). Проверка: GET /v1/payouts/{id}.

Ошибки

Тело ошибки: {"error": "описание"}.

КодПричина
400Неверный JSON или параметры
401Нет или неверный API-ключ
404Объект не найден
422Недостаточно средств на балансе
502Временная ошибка — повторите запрос позже (для выплат — с тем же request_id)

Чек-лист перед запуском

  • API-ключ хранится только на сервере.
  • Задан Callback URL (https) и секрет; подпись проверяется на каждом вебхуке.
  • Перед выдачей товара статус проверяется через GET /v1/invoices/{id}.
  • Обработчик вебхуков идемпотентен (повтор события не выдаёт товар дважды).
  • Клиенту показывается точная сумма amount_to_pay и сеть.
  • Указан адрес выплат и настроены правила выплат.
  • Проведён тестовый платёж на минимальную сумму.