Skip to main content

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 у такому порядку:
  1. HTTP header x-public-key без урахування регістру. Якщо header присутній, використовується він, а тіло запиту для public key не перевіряється.
  2. Якщо header відсутній, використовується поле publicKey із декодованого JSON payload, тобто всередині data після base64 decode.
Якщо public key не знайдено в жодному місці, запит відхиляється з HTTP 401 Missing public key. Обидва варіанти допустимі. Оберіть один варіант для кожного запиту. Рекомендований варіант — передавати public key у header: так signed payload містить лише бізнес-поля, а correlation у логах стає простішою. Header form (recommended):
Decoded payload містить лише DTO-поля:
Payload form (header omitted):
Decoded payload містить поле publicKey разом із DTO-полями:
Підпис розраховується за тим самим base64-рядком, який читатиме сервер. Тому publicKey має бути доданий у JSON до encoding і signing. Якщо додати publicKey після підписання, verification не пройде.

2.3. Типи ключів

API key мерчанта має одне з двох значень keyType. Сервер обирає алгоритм verification за merchant record, пов’язаним із public key. Інтегратор не передає key type явно. ED25519 використовується за замовчуванням для нових ключів. LEGACY підтримується для історичних інтеграцій. Ці два алгоритми дають різні підписи для одного й того самого payload, тому використовуйте обраний алгоритм послідовно. Кроки:
  1. Зберіть JSON payload із DTO-полями, див. endpoint sections нижче.
  2. json = JSON.stringify(payload)
  3. data = base64(utf8_bytes(json))
  4. signatureBytes = ed25519.sign(utf8_bytes(data), privateKeyBytes) — входом є саме base64 string, а не початковий JSON і не raw bytes JSON.
  5. signature = base64(signatureBytes)
  6. Надішліть POST { data, signature } з header x-public-key: <hex>.
Node.js example (ED25519):

2.5. Algorithm B — LEGACY (SHA-256 with shared secret)

Кроки:
  1. Зберіть JSON payload.
  2. data = base64(utf8_bytes(JSON.stringify(payload)))
  3. hexDigest = sha256_hex(privateKey + data) — string concatenation secret і base64 payload.
  4. signature = base64(utf8_bytes(hexDigest)) — base64-encode застосовується до рядка hex-символів, а не до raw 32-byte digest.
  5. Надішліть POST { data, signature } з header x-public-key: <hex>.
Node.js example (LEGACY):

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 ключем.
Якщо для ключа налаштовано IP allowlist, запити з будь-якої іншої IP address відхиляються.

2.7. Error codes

Error responses використовують числове поле code. Найважливіші коди для authentication і validation layer:

3. Request / Response Envelope

Кожен response від authenticated endpoint-ів повертається у wrapper:
Non-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:
Response:
У цей момент баланс мерчанта змінюється: 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

Платформа повідомляє мерчанта про зміни статусу через HTTP POST на 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 має:
  1. Перевірити replay protection. Відхиляйте delivery, якщо id вже був оброблений. Зберігайте processed IDs у database; in-memory set буде втрачено після restart.
  2. Перевірити timestamp. Відхиляйте delivery, якщо Math.abs(now - delivered_at) > 16 minutes. Вікно 16 хвилин покриває maximum delivery time з урахуванням retries.
  3. Recompute and compare signature за { id, delivered_at, event }.
  4. Зберегти id лише після успішного проходження всіх трьох перевірок.
Handler має бути idempotent: одна й та сама delivery може прийти більше одного разу під час retries. Verification (ED25519):
Verification (LEGACY):