Документация разработчика
Zenith API
Рабочие методы текущего API: инвойс, checkout, вебхук, кабинет, вывод и свап. Ниже — контракты, подпись и ответы, без чужих SDK.
API-ключи
Кабинет — это аккаунт. Мерчант (магазин/бот) получает отдельную пару ключей при создании. HMAC и вебхук считаются секретом этого мерчанта. Ключ кабинета после регистрации не используйте для приёма платежей разных проектов.
Ключ: страница мерчанта в кабинете. Secret показывается один раз (создание или «Сменить ключ»). Ротация: POST /api/v1/shops/{id}/rotate-keys с Bearer JWT.
POST /api/v1/shops
Authorization: Bearer <jwt>
Content-Type: application/json
{ "name": "Shop One", "kind": "website", "project_url": "https://shop.example" }
# 201
{
"id": "…",
"name": "Shop One",
"status": "pending",
"api_key": "pk_…",
"api_secret": "sk_…",
"webhook_url": null
}- Платежи: X-Api-Key = pk_ мерчанта. shop_id в теле не нужен — инвойс сам привяжется.
- Вебхук: PATCH /api/v1/shops/{id} { "webhook_url": "https://…" }.
- Логин кабинета: POST /api/v1/merchants/login → access_token.
- Telegram Login: POST /api/v1/merchants/telegram-login.
Формат запроса
Подпись обязательна, если REQUIRE_API_SIGNATURE=true или вы уже передали X-Signature. Тело в HMAC — сырые байты запроса, не пересобранный JSON.
- X-Api-Key — публичный ключ pk_…
- X-Timestamp — unix time, допуск signature_ttl_seconds (по умолчанию 300).
- X-Signature — hex HMAC-SHA256(secret, timestamp + "\n" + METHOD + "\n" + path + "\n" + body).
- path — полный путь, например /api/v1/invoice/create, без query.
- GET: body пустая строка.
- Кабинет: Authorization: Bearer <jwt> — HMAC не нужен.
BODY='{"amount_usd":"10.00","order_id":"ORD-1042","network":"TRC20"}'
TS=$(date +%s)
PATH='/api/v1/invoice/create'
SIG=$(printf '%s\nPOST\n%s\n%s' "$TS" "$PATH" "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $2}')
curl -sS -X POST "https://api.example.com$PATH" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"import hashlib, hmac, json, time, urllib.request
body = json.dumps({"amount_usd": "10.00", "order_id": "ORD-1042", "network": "TRC20"}, separators=(",", ":"))
path = "/api/v1/invoice/create"
ts = str(int(time.time()))
msg = f"{ts}\nPOST\n{path}\n{body}"
sig = hmac.new(api_secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(
"https://api.example.com" + path,
data=body.encode(),
headers={"Content-Type": "application/json", "X-Api-Key": api_key, "X-Timestamp": ts, "X-Signature": sig},
method="POST",
)
print(urllib.request.urlopen(req).read().decode())const crypto = require("crypto");
const body = JSON.stringify({ amount_usd: "10.00", order_id: "ORD-1042", network: "TRC20" });
const path = "/api/v1/invoice/create";
const ts = String(Math.floor(Date.now() / 1000));
const sig = crypto.createHmac("sha256", apiSecret).update(`${ts}\nPOST\n${path}\n${body}`).digest("hex");
const res = await fetch("https://api.example.com" + path, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": apiKey, "X-Timestamp": ts, "X-Signature": sig },
body,
});Лимит создания инвойсов: 30 запросов в минуту на IP. Ошибки: 401 подпись/ключ, 409 email занят, 422 валидация, 429 лимит.
Payform
- Бэкенд создаёт инвойс.
- Покупатель открывает checkout_url.
- Платит USDT по QR/адресу.
- Вы получаете вебхук invoice.paid и/или polling статуса.
Покупатель не регистрируется. Таймер жизни инвойса — 30 минут.
Host-to-host
Тот же POST /invoice/create. Не открывайте checkout_url — покажите deposit_address, amount_crypto, asset, network, payment_memo (если есть), expires_at. QR соберите сами (данные = адрес или адрес|сумма).
- Статус: GET /api/v1/invoice/{id} с HMAC или публичный GET /api/v1/pay/{token}/status.
- token — хвост checkout_url после /pay/.
- Итог оплаты дублируйте вебхуком, не только polling.
Виджет
Инвойс всё равно создаётся на вашем бэкенде. На страницу отдайте только checkout_url. Скрипт: GET /widget.js (проксируется с API).
<div id="zenith-pay"></div> <script src="https://ваш-домен/widget.js" data-checkout="CHECKOUT_URL" data-target="#zenith-pay" data-height="640"></script>
Создание инвойса
| Поле | Тип | Описание |
|---|---|---|
| amount_usd | string decimal > 0 | Сумма в USD, до 2 знаков |
| order_id | string 1–128 | Ваш id заказа, уникален на мерчанта |
| network | TRC20 | TON | Сеть USDT, по умолчанию TRC20 |
| return_url | url? | Куда вернуть покупателя после оплаты |
| shop_id | uuid? | Не нужен, если X-Api-Key — ключ мерчанта. Иначе привязка к одобренному магазину |
Повтор с тем же order_id идемпотентен: 200 и старый инвойс, не дубль. Сумма в USDT = USD × (1 + спред курса, по умолчанию 1%). TON: уникальный subwallet без memo, если настроена мнемоника казны; иначе общий адрес + payment_memo.
{
"id": "7c2e…",
"order_id": "ORD-1042",
"amount_usd": "10.00",
"amount_crypto": "10.100000",
"asset": "USDT",
"network": "TRC20",
"deposit_address": "T…",
"payment_memo": null,
"status": "pending",
"checkout_url": "https://…/pay/{token}",
"return_url": null,
"shop_id": null,
"expires_at": "2026-09-20T18:10:00+00:00",
"tx_hash": null,
"received_crypto": null,
"confirmations": 0,
"created_at": "2026-09-20T17:40:00+00:00"
}Статус платежа
- Список: GET /api/v1/invoice
- Один: GET /api/v1/invoice/{id}
- Публично по токену checkout: GET /api/v1/pay/{token}/status
Сканер сам ищет перевод по адресу инвойса: USDT/USDC (TRC-20, TON, ERC-20, BEP-20, SOL), BTC, ETH, TON, TRX, BNB, SOL, LTC, DOGE, BCH, DASH, AVAX, POL, DAI, SHIB. Допуск суммы 0.5%. Подтверждения зависят от сети (TRC-20 — 19, TON — 1, BTC — 2, ETH — 12). DEBUG: POST /api/v1/invoice/{id}/simulate-pay.
Checkout
Готовая payform. Polling публичного статуса. После paid можно увести на return_url.
Платёжные ссылки
Сначала POST /api/v1/shops (модерация admin). После approved:
POST /api/v1/shops/{shop_id}/payment-links
{
"shop_id": "{shop_id}",
"pay_type": "invoice",
"amount": "25.00",
"amount_currency": "USD",
"network": "TON",
"order_id": "site-1001",
"return_url": "https://shop.example/thanks"
}pay_type: invoice | link. Пустой order_id генерируется (inv-… / lnk-…).
Вебхук оплаты
Событие invoice.paid. 3 попытки, пауза 60 с, пока ответ не 2xx. URL и подпись — у мерчанта (shop), иначе fallback на кабинет.
- Заголовки: X-Timestamp, X-Signature, X-Event=invoice.paid, X-Event-Id=invoice_id
- Подпись: HMAC-SHA256(api_secret, timestamp + "." + raw_body) — не та же строка, что у входящих API-запросов.
- Повтор: POST /api/v1/invoice/{id}/webhook (только paid).
{
"event": "invoice.paid",
"event_id": "7c2e…",
"invoice_id": "7c2e…",
"order_id": "ORD-1042",
"amount_usd": "10.00",
"amount_crypto": "10.100000",
"asset": "USDT",
"network": "TRC20",
"tx_hash": "…",
"status": "paid",
"paid_at": "2026-09-20T17:41:02+00:00"
}import hmac, hashlib
expected = hmac.new(api_secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, header_signature)Пополнение кабинета
Это пополнение личного/бизнес леджера, не инвойс покупателя. Аналог «статического» адреса мерчанта в кабинете.
GET /api/v1/dashboard/deposit-address?asset=USDT&network=TRC20
# { "network": "TRC20", "asset": "USDT", "address": "T…", "memo": null }Список монет и сетей: GET /api/v1/dashboard/coins.
Балансы и история
- GET /api/v1/dashboard/overview?account=personal|business
- GET /api/v1/dashboard/history?scope=all|personal|merchants
- GET /api/v1/dashboard/analytics
- GET /api/v1/dashboard/treasury — адреса казны для ончейн-выводов
Перевод между счетами
POST /api/v1/dashboard/transfer
{ "from_account": "business", "to_account": "personal", "asset": "USDT", "amount": "50" }Счета должны отличаться. Списывается баланс выбранного актива на from_account.
Вывод в сеть
POST /api/v1/dashboard/send
{ "asset": "USDT", "network": "TRC20", "address": "T…", "amount": "10.5", "comment": "vendor" }Сначала бизнес-счёт, если не хватает — личный. ENABLE_ONCHAIN_PAYOUTS=true и USE_MOCK_CHAIN=false: воркер шлёт USDT с казначейства (TRC-20 / TON jetton). Иначе заявка закрывается offchain-хешем. Свип депозитов: ENABLE_DEPOSIT_SWEEP.
Конвертация
GET /api/v1/dashboard/quote?from_asset=USDT&to_asset=TON&amount=10
POST /api/v1/dashboard/convert
{ "account": "personal", "from_asset": "USDT", "to_asset": "TON", "amount": "10" }Курс TON с CoinGecko/Coinbase плюс спред платформы. Это не ончейн DEX.
Автосвап депозита
- GET /api/v1/dashboard/swap-rules
- POST /api/v1/dashboard/swap-rules { from_asset, to_asset, account, enabled }
- POST /api/v1/dashboard/swap-rules/{id}/toggle
- DELETE /api/v1/dashboard/swap-rules/{id}
Монеты и сети
| Символ | Сети в кабинете |
|---|---|
| USDT | TRC20, TON, ERC20, BEP20 |
| USDC | ERC20, TRC20, SOL |
| BTC | BTC |
| ETH | ERC20 |
| TON / GRAM | TON |
| TRX | TRC20 |
| BNB | BEP20 |
| SOL | SOL |
| LTC / DOGE / XMR / AVAX / POL / BCH / DAI / DASH / SHIB | свои сети в /dashboard/coins |