Интеграция по API
REST API, JSON, HTTPS. Базовый URL:
https://zoltopay.com/gw
Быстрый старт
- Зарегистрируйтесь — API-ключ (формат
gw_…) покажется сразу после регистрации. Потерянный ключ можно перевыпустить в кабинете. - В кабинете → Настройки: укажите адрес выплат, правила выплат и Callback URL, затем нажмите «Создать новый секрет» для подписи вебхуков.
- Ваш сервер создаёт инвойс → перенаправляет клиента на
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.Tron | USDT | TRON (TRC-20) | 10 USD |
USDT.BNBSmartChain | USDT | BNB Chain (BEP-20) | 10 USD |
USDC.BNBSmartChain | USDC | BNB Chain (BEP-20) | 10 USD |
USDT.Polygon | USDT | Polygon | 10 USD |
USDC.Polygon | USDC | Polygon | 10 USD |
USDT.Arbitrum | USDT | Arbitrum One | 10 USD |
USDC.Arbitrum | USDC | Arbitrum One | 10 USD |
USDT.Ethereum | USDT | Ethereum (ERC-20) | 50 USD |
USDC.Ethereum | USDC | Ethereum (ERC-20) | 50 USD |
Значение currency_id передаётся при создании инвойса и выплаты. Клиенту обязательно покажите сеть: перевод в другой сети автоматически не зачисляется.
Создание инвойса
POST /v1/invoices
| Поле | Тип | Описание | |
|---|---|---|---|
order_id | string | обязательно | Ваш номер заказа. Уникален в пределах аккаунта. |
amount | string | обязательно | Сумма в валюте currency. Минимум 10 USD. |
currency | string | Монета и сеть, напр. USDT.Tron. По умолчанию — монета выплат из настроек. | |
ttl_minutes | int | Срок жизни инвойса, по умолчанию 60. | |
email | string | Email клиента (необязательно). | |
return_url | string | Куда вернуть покупателя со страницы оплаты после оплаты, 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_id | string | обязательно | Ваш уникальный ключ идемпотентности. Повтор с тем же request_id не создаст вторую выплату. |
address | string | обязательно | Адрес получателя. |
amount | string | обязательно | Сумма к получению. С баланса спишется сумма + сетевой сбор. Минимум 20 USD. |
currency | string | По умолчанию — монета выплат из настроек. |
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и сеть. - Указан адрес выплат и настроены правила выплат.
- Проведён тестовый платёж на минимальную сумму.