Off-Ramp — довідник API для інтеграторів
1. Загальні правила
- Base URL надається платформою.
- Усі endpoint-и Off-Ramp приймають і повертають
application/json. - Усі authenticated endpoint-и використовують метод
POST, включно з read-only операціями на кшталтratesабоwithdrawals/list, тому що тіло запиту має містити підписаний envelope. - Грошові числові значення повертаються як рядки, щоб зберегти точність decimal-значень, наприклад
"25.18"або"39.7059". - Часові мітки повертаються як ISO-8601 UTC рядки, наприклад
"2026-05-04T10:00:00.000Z".
2. Автентифікація та підпис запитів
Кожен authenticated request має бути надісланий як підписаний envelope:data — це base64 від JSON payload. signature — це base64 криптографічного підпису, розрахованого за base64-рядком data. Конкретний алгоритм залежить від типу API-ключа, див. розділ 2.3.
2.1. Обов’язкові headers
2.2. Де передавати public key — у header чи payload
Сервер визначає public key у такому порядку:- HTTP header
x-public-keyбез урахування регістру. Якщо header присутній, використовується він, а тіло запиту для public key не перевіряється. - Якщо header відсутній, використовується поле
publicKeyіз декодованого JSON payload, тобто всерединіdataпісля base64 decode.
401 Missing public key.
Обидва варіанти допустимі. Оберіть один варіант для кожного запиту. Рекомендований варіант — передавати public key у header: так signed payload містить лише бізнес-поля, а correlation у логах стає простішою.
Header form (recommended):
publicKey разом із DTO-полями:
publicKey має бути доданий у JSON до encoding і signing. Якщо додати publicKey після підписання, verification не пройде.
2.3. Типи ключів
API key мерчанта має одне з двох значеньkeyType. Сервер обирає алгоритм verification за merchant record, пов’язаним із public key. Інтегратор не передає key type явно.
ED25519 використовується за замовчуванням для нових ключів. LEGACY підтримується для історичних інтеграцій. Ці два алгоритми дають різні підписи для одного й того самого payload, тому використовуйте обраний алгоритм послідовно.
2.4. Algorithm A — ED25519 (recommended)
Кроки:- Зберіть JSON payload із DTO-полями, див. endpoint sections нижче.
json = JSON.stringify(payload)data = base64(utf8_bytes(json))signatureBytes = ed25519.sign(utf8_bytes(data), privateKeyBytes)— входом є саме base64 string, а не початковий JSON і не raw bytes JSON.signature = base64(signatureBytes)- Надішліть
POST { data, signature }з headerx-public-key: <hex>.
2.5. Algorithm B — LEGACY (SHA-256 with shared secret)
Кроки:- Зберіть JSON payload.
data = base64(utf8_bytes(JSON.stringify(payload)))hexDigest = sha256_hex(privateKey + data)— string concatenation secret і base64 payload.signature = base64(utf8_bytes(hexDigest))— base64-encode застосовується до рядка hex-символів, а не до raw 32-byte digest.- Надішліть
POST { data, signature }з headerx-public-key: <hex>.
2.6. API key permissions and IP allowlist
API key, випущений у merchant portal, містить список permissions. Off-Ramp використовує один із них:POST /merchant/api/v1/express/withdrawalsпотребує увімкнений permission Fiat Withdraw.- Усі інші Off-Ramp endpoint-и потребують лише коректного підпису активним і не revoked ключем.
2.7. Error codes
Error responses використовують числове полеcode. Найважливіші коди для authentication і validation layer:
3. Request / Response Envelope
Кожен response від authenticated endpoint-ів повертається у wrapper:200 responses повертають стандартний NestJS error envelope:
4. Endpoint Reference
Усі endpoint-и нижче використовують однакову authentication model: signed{ data, signature } envelope, header x-public-key, Content-Type: application/json. Лише POST /merchant/api/v1/express/withdrawals додатково потребує permission Fiat Withdraw і перевіряється за API-key IP allowlist.
4.1. POST /merchant/api/v1/express/rates
Повертає найкращі доступні merchant-facing rates за bank link.
Decoded payload:
Response:
id — це rateId, який потрібно передати в withdrawals під час створення order. rate — fiat amount за 1 USDT. Для заданого fiatAmount з мерчанта буде списано fiatAmount / rate USDT. Ця сума повертається як usdtTotal із withdrawals.
4.2. POST /merchant/api/v1/express/banks
Повертає список банків, що підтримуються для вказаної fiat currency.
Decoded payload:
Response:
4.3. POST /merchant/api/v1/express/currencies
Повертає список fiat currencies, що підтримуються вказаним bank.
Decoded payload:
Response:
4.4. POST /merchant/api/v1/express/bank-link
Повертає schema полів recipient-data, потрібних для order за bank link. Використовуйте цей endpoint перед withdrawals, щоб зрозуміти, які поля, наприклад card number або phone, потрібно зібрати в end-user.
Decoded payload:
Response:
fieldType може бути TEXT, NUMBER або MASKED. name кожного поля — це key, який інтегратор має використати в recipientData під час виклику withdrawals.
4.5. POST /merchant/api/v1/express/withdrawals
Створює fiat withdrawal. Система блокує USDT на балансі мерчанта та надсилає order P2P-партнеру.
Required permission: Fiat Withdraw. Endpoint перевіряється за API-key IP allowlist.
Decoded payload:
Example payload before signing:
available -= usdtTotal, locked += usdtTotal. Funds are released only when transaction reaches COMPLETED (consumed) or CANCELLED (refunded). Повний balance flow описано в розділі 5.
4.6. POST /merchant/api/v1/express/withdrawals/list
Paginated list Off-Ramp withdrawals мерчанта.
Decoded payload:
Response:
4.7. POST /merchant/api/v1/express/withdrawals/detail
Деталі одного withdrawal.
Decoded payload:
Response:
5. Withdrawal Status Lifecycle
Merchant-visible status flow для Express transaction:usdtTotal і exchangeRate, повернуті withdrawals, фіксуються під час створення transaction і не змінюються протягом усього lifecycle transaction.
6. Outbound Webhooks
Платформа повідомляє мерчанта про зміни статусу через HTTPPOST на webhook URL, налаштований мерчантом. Delivery asynchronous і повторюється при failure.
6.1. Event types
Events
express::order.* і express::partner.* є internal і не доставляються на merchant webhooks.
6.2. Webhook payload
6.3. Verifying the webhook
Signature covers payload object без поляsignature, тобто { id, delivered_at, event }, і створюється тим самим алгоритмом, що request signature для merchant key, див. розділ 2.3.
Коректна verification implementation має:
- Перевірити replay protection. Відхиляйте delivery, якщо
idвже був оброблений. Зберігайте processed IDs у database; in-memory set буде втрачено після restart. - Перевірити timestamp. Відхиляйте delivery, якщо
Math.abs(now - delivered_at) > 16 minutes. Вікно 16 хвилин покриває maximum delivery time з урахуванням retries. - Recompute and compare signature за
{ id, delivered_at, event }. - Зберегти
idлише після успішного проходження всіх трьох перевірок.