LumaPayAPI v1
UK RU EN
Merchant Integration Reference

Приймання й виплати через один узгоджений контракт

P2P-платіжна система: створюйте заявки на приймання й виплати, звіряйте статуси, отримуйте вебхуки з єдиним канонічним форматом. Усі суми — у звичайних одиницях, 2 знаки після коми.

BASE URL https://lumapay.org/api/v1

Огляд

Кожен запит підписується й іде на версійований шлях /api/v1/…. Версія живе у шляху: коли зʼявиться v2, ваша інтеграція на v1 продовжить працювати без змін. Усередині версії ми лише додаємо поля — ніколи не прибираємо й не перейменовуємо.

МетодШляхПризначення
POST/ordersСтворити заявку на приймання, отримати реквізит для платника
GET/orders/{uuid}Поточний стан заявки
POST/orders/{uuid}/paidПлатник оплатив заявку IBAN або quasi: передати квитанцію трейдеру (для iban обовʼязкова, для quasi - ні)
POST/payoutsСтворити виплату на картку або IBAN отримувача
GET/payouts/{uuid}Поточний стан виплати
POST/payouts/{uuid}/cancelСкасувати виплату, яку ще не взяв трейдер
GET/payouts/{uuid}/receipts/{index}Файл чека виплати
GET/balanceБаланс і оборот у розрізі валют
GET/crypto/balanceАдреси й баланс USDT ваших крипто-гаманців
POST/crypto/payoutsСтворити виплату USDT (TRC20 або BEP20)
GET/crypto/payouts/{id}Поточний стан крипто-виплати
POST/crypto/payouts/{id}/cancelСкасувати крипто-виплату до відправки

Автентифікація & підпис

До кожного запиту додайте два заголовки. Порядок перевірки на сервері: ліміт частоти → цілісність тіла → ключ → підпис, тож помилка називає саме свою причину.

ЗаголовокЗначення
X-API-KeyВаш api_key (публічний ідентифікатор мерчанта)
X-SignatureHMAC-SHA256(<сире тіло запиту>, api_secret) у hex
Content-Typeapplication/json

Підписується точні байти тіла, які ви відправляєте — серіалізуйте JSON один раз і підпишіть саме цей рядок. У GET-запитів тіло порожнє, тож X-Signature — це HMAC порожнього рядка з api_secret.

Два різні секрети — не переплутайте

api_secret підписує ваші запити до нас. webhook_secret потрібен, щоб ви перевіряли наші колбеки. Перевірка колбека не тим секретом — найдорожча помилка інтеграції.

Приклад підпису (bash)
# тіло, яке відправляємо
BODY='{"order_id":"A-1001","amount":1500.00,"currency":"UAH"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" | sed 's/^.*= //')

curl -X POST https://lumapay.org/api/v1/orders \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Signature: $SIG" \
  -d "$BODY"

Додатково: якщо у профілі задано білий список IP, запити приймаються лише з нього (перевіряється REMOTE_ADDR, X-Forwarded-For ігнорується).

Ідемпотентність

Заявки й виплати ідемпотентні за парою (мерчант, order_id). Точний повтор того самого запиту повертає оригінальний обʼєкт (не створює дубль). Той самий order_id зі зміненим тілом (сума, валюта, платник / картка отримувача) — це конфлікт 409 idempotency_conflict, у відповіді буде наявний обʼєкт.

Практика

Ставте власний order_id й безпечно ретрайте мережеві збої тим самим запитом — дубля не буде. callback_url не входить у відбиток: змінити адресу доставки й повторити — це та сама заявка.

Заявки на приймання

POST/api/v1/orders

Створює заявку payin і повертає реквізит (картку), який показуєте платнику. Комісія утримується з суми (inclusive): мерчанту зараховується amount − commission.

Параметри запиту
ПолеТипОпис
order_idstringrequiredВаш зовнішній номер. Ключ ідемпотентності. До 255 символів.
amountnumberrequiredСума в звичайних одиницях, ≥ 0.01, 2 знаки.
currencystring(3)requiredISO-код валюти. Має бути активна й увімкнена для приймання.
payer_idstringoptionalІдентифікатор платника. Бере участь у роутингу й ідемпотентності.
payer_emailstringoptionalПошта платника, для антифроду. Некоректна адреса не зберігається, помилки немає.
payer_phonestringoptionalТелефон платника з кодом країни, наприклад +380671234567, 7-15 цифр. Пробіли, дужки й дефіси прибираються; невідповідний номер не зберігається, помилки немає.
payer_bankstringoptionalБанк платника. Використовується лише коли увімкнено правило збігу банку.
methodstringoptionalcard (за замовчуванням), iban - оплата переказом на IBAN, або quasi - оплата за посиланням, лише поповнення. Для quasi потрібен підключений тариф, інакше 422 method_not_enabled.
callback_urlstring(url)optionalАдреса колбека для цієї заявки. Порожньо → адреса з кабінету.
return_urlstring(url)optionalАдреса кнопки повернення на ваш сайт на сторінці оплати (payment_url). http/https, до 255 знаків.
Запит
{
  "order_id": "A-1001",
  "amount": 1500.00,
  "currency": "UAH",
  "payer_id": "user-42"
}
Відповідь · 201
{
  "id": "7af7c1df-0629-42d4-9068-df6500b3acd6",
  "order_id": "A-1001",
  "status": "awaiting_payment",
  "amount": 1500.00,
  "amount_settled": null,
  "commission": 30.00,
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "payer_id": "user-42",
  "requisite": {
    "card_number": "5375 4141 0000 1234",
    "card_holder": "ПЕТРЕНКО ІВАН",
    "bank": "PrivatBank",
    "type": "card"
  },
  "created_at": "2026-08-09T10:00:00+00:00",
  "expires_at": "2026-08-09T10:15:00+00:00",
  "completed_at": null,
  "payment_url": "https://pay.example.com/p/7af7c1df-0629-42d4-9068-df6500b3acd6.3f9c2a7e5b1d4c8a9e6f0b2d7a4c1e8f"
}

Показуйте requisite.card_number платнику; чекайте на вебхук order.completed (не поллінг). Поле id — це {uuid} для запиту статусу.

При "method": "iban" у requisite: type = iban, card_number = null, iban (UA…, 29 знаків), card_holder - найменування/ПІБ отримувача, bank_code - ЄДРПОУ/МФО/РНОКПП, payment_purpose - призначення платежу або null. Вільного IBAN немає - 503 no_requisite_available, як і для картки.

При "method": "quasi" у requisite: type = quasi, pay_link - посилання, за яким платить платник, card_number і card_holder = null. Метод працює лише на поповнення і лише з підключеним тарифом: без нього - 422 method_not_enabled.

Якщо для методу підключено готову сторінку оплати, у відповіді є payment_url - посилання, на яке можна одразу перенаправити платника: реквізити, таймер і кнопку «Я оплатив» показує сторінка. Поля немає - показуйте requisite у себе. Необовʼязковий return_url у запиті (http/https, до 255 знаків) - адреса кнопки повернення на ваш сайт.

GET/api/v1/orders/{uuid}

Повертає поточний стан заявки (той самий обʼєкт, що й POST /orders). Заявка іншого мерчанта невидима навіть за відомим UUID.

Статуси заявки
awaiting_paymentРеквізит видано, чекаємо оплату платника
processingОплату видно, триває звірка зарахування
completedУспішно зараховано — вебхук order.completed
expiredНе оплачено вчасно — вебхук order.expired
cancelledСкасовано оператором — вебхук order.cancelled
disputedВідкрито спір; рішення закриє заявку — вебхук order.disputed

Якщо за заявкою є спір, у відповіді є поле dispute - той самий обʼєкт, що повертає POST /orders/{uuid}/dispute, за останнім спором заявки. Немає спору - немає й поля.

POST/api/v1/orders/{uuid}/cancel

Зняти заявку, яку ще не оплатили: платник передумав або оператор закриває її з вашого боку. Картка трейдера одразу звільняється під наступну заявку. У відповідь приходить той самий обʼєкт заявки зі статусом cancelled, і на ваш callback_url іде подія order.cancelled.

Тіло не обовʼязкове. Можна передати reason — причина потрапить у вебхук, у журнал і в картку заявки, де її бачить оператор платіжної системи, тож пишіть зрозумілий текст, а не службовий код; порожнє тіло {} теж приймається, тоді запишеться «Cancelled by merchant». Підписуйте рівно той рядок, який надсилаєте.

Скасувати можна лише неоплачену заявку — статуси pending і awaiting_payment. Щойно платник переказав гроші (processing) або відкрито спір, скасування через API вже не працює: кошти могли надійти на картку трейдера, і такий випадок закриває підтримка. Відповідь — 409 order_not_cancellable і поточний стан заявки в полі order.

POST/api/v1/orders/{uuid}/paid

Лише для заявок з method=iban і method=quasi. Платник зробив переказ і натиснув «Я оплатив» у вашій формі: передайте його квитанцію, ми покажемо її трейдеру. Для quasi квитанція не обовʼязкова: запит приймається і без receipts, далі платіж підтверджує трейдер. Заявка переходить у processing і чекає підтвердження трейдера; таймер expires_at для неї більше не діє. Результат прийде звичайним вебхуком order.completed або order.disputed.

Параметри запиту
ПолеТипОпис
receiptsarrayтак, для quasi - ніВід 1 до 3 файлів, кожен - рядок base64 (можна з префіксом data:...;base64,). JPG, PNG або PDF, до 5 МБ кожен
{"receipts": ["iVBORw0KGgoAAAANSUhEUgAA..."]}

Повторний виклик додає квитанції до вже переданих, разом не більше 3 на заявку. У відповідь - обʼєкт заявки з полями payer_paid_at і receipts_count.

Помилки: 422 method_not_supported - заявка не IBAN і не quasi; 422 receipt_invalid, receipt_type_not_supported, receipt_too_large, receipt_limit - файл не прийнято; 409 order_not_active - заявка вже закрита, протермінована або в спорі (поточний стан у полі order).

POST/api/v1/orders/{uuid}/dispute

Платник заплатив, а заявку не зараховано: протермінувалась, зарахована на меншу суму або ще в роботі. Передайте його квитанцію - відкриється спір, як через Telegram-бота чи кабінет. Працює для заявок будь-якого методу і статусу. Замість нашого uuid у шляху можна передати ваш order_id.

Далі спір іде до трейдера. Підтвердив - заявку зараховано автоматично або після перевірки підтримкою; гроші приходять звичайним вебхуком order.completed або order.amount_corrected. Рішення по спору - вебхук dispute.resolved.

Параметри запиту
ПолеТипОпис
receiptsarrayтакВід 1 до 3 файлів, кожен - рядок base64 (можна з префіксом data:...;base64,). JPG, PNG або PDF (і PDF з електронним підписом банку), до 5 МБ кожен
commentstringніДо 2000 символів: що сталося, сума і час платежу. Бачить підтримка і трейдер
{"receipts": ["JVBERi0xLjQK..."], "comment": "Оплатив 1500 о 14:05, заявка протермінувалась"}

Відповідь - 201, якщо спір відкрито, і 200, якщо за заявкою вже є відкритий спір: тоді нові квитанції додаються до нього. Ту саму квитанцію вдруге не додаємо, тож запит можна безпечно повторити після таймауту; якщо спір уже вирішено, повтор з тими самими квитанціями поверне його ж (200), а з новою квитанцією відкриє новий спір.

{
  "dispute": {
    "id": 731,
    "number": "100731",
    "status": "open",
    "outcome": null,
    "created_at": "2026-10-18T14:20:11+00:00",
    "resolved_at": null,
    "already_credited": false
  },
  "order": { ... }
}
statusopen - розбір триває, resolved - рішення є
outcomemerchant - зараховано повністю, partial - зараховано іншу суму, trader - оплату не підтверджено; null, поки спір відкритий
already_creditedtrue - заявку вже зараховано повністю; спір відкрито (це може бути другий платіж), підтримка бачить позначку

Помилки: 422 receipt_invalid, receipt_type_not_supported, receipt_too_large, receipt_limit - файл не прийнято; 409 dispute_not_available - тестова заявка пісочниці.

Виплати

POST/api/v1/payouts

Створює виплату на картку або на IBAN отримувача. Комісія додається зверху (on-top): з мерчанта списується amount + commission = amount_charged.

Параметри запиту
ПолеТипОпис
order_idstringrequiredВаш зовнішній номер виплати. Ключ ідемпотентності.
amountnumberrequiredСума переказу, ≥ 0.01, 2 знаки.
currencystring(3)requiredISO-код. Активна й увімкнена для виплат.
methodstringoptionalcard (за замовчуванням) або iban - переказ на рахунок IBAN. Виплату на IBAN трейдер виконує й підтверджує вручну, як і на картку.
recipient_cardstringrequiredНомер картки отримувача (до 32). Для method=iban не потрібен.
recipient_namestringoptionalІмʼя отримувача. Бере участь в ідемпотентності. Для method=iban обовʼязкове: ПІБ або найменування компанії.
recipient_bankstringoptionalБанк отримувача.
recipient_ibanstringoptionalIBAN отримувача, лише для method=iban. Перевіряються формат і контрольна сума; пробіли допускаються. Український IBAN - 29 знаків.
recipient_tax_idstringoptionalЄДРПОУ (8 цифр) або ІПН/РНОКПП (10 цифр) отримувача, лише для method=iban.
payment_purposestringoptionalПризначення платежу, до 140 знаків, лише для method=iban.
callback_urlstring(url)optionalАдреса колбека для цієї виплати.
Відповідь · 201
{
  "id": "b1e2c3d4-5678-90ab-cdef-1234567890ab",
  "order_id": "P-2001",
  "status": "pending",
  "amount": 1000.00,
  "commission": 15.00,
  "amount_charged": 1015.00,
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "method": "card",
  "recipient": { "card_number": "4149 •••• •••• 3179", "name": "IVAN P.", "bank": "A-Bank" },
  "receipt_attached": false,
  "receipts": [],
  "created_at": "2026-08-09T10:00:00+00:00",
  "expires_at": "2026-08-09T14:00:00+00:00",
  "completed_at": null
}

Для method=iban у відповіді method: "iban", у recipient - iban (замаскований), tax_id і payment_purpose, а card_number - null. Комісія - ставка виплати на IBAN з вашого тарифу; якщо окремої ставки немає, як виплата на картку. Реквізити IBAN, ЄДРПОУ/ІПН і призначення беруть участь в ідемпотентності.

GET/api/v1/payouts/{uuid}

Стан виплати (той самий обʼєкт, що й при створенні), scoped до вашого мерчанта.

Статуси виплати
pendingУ спільному пулі, чекає трейдера
assigned / processingУзято в роботу — вебхук payout.processing
completedВиконано — вебхук payout.completed, є tx_receipt
expiredСтрок вичерпано
cancelledСкасовано
disputedВідкрито спір
GET/api/v1/payouts/{uuid}/receipts/{index}

Файл чека виплати, тип файлу - у заголовку Content-Type. Готові посилання лежать у полі receipts обʼєкта виплати та вебхука: чеків буває до 5, {index} - номер із посилання. Запит підписується як будь-який GET: X-API-Key і X-Signature від порожнього тіла. Чеки доступні у статусах processing, completed і disputed.

Помилки: 404 payout_not_found - виплати немає або вона не ваша; 404 receipt_not_found - чека з таким номером немає.

POST/api/v1/payouts/{uuid}/cancel

Відкликати виплату, поки її ніхто не взяв. Тіло порожнє — надішліть {} і підпишіть саме ці два байти. У відповідь приходить той самий обʼєкт виплати зі статусом cancelled, і на ваш callback_url іде подія payout.cancelled.

Щойно виплату взяв трейдер, скасувати її через API вже не можна: він міг почати переказ. Такий випадок закриває підтримка — відповідь 409 payout_not_cancellable і поточний стан виплати в полі payout.

Баланс

GET/api/v1/balance

Баланс і оборот у розрізі валют, рахуються так само, як у кабінеті: зараховано − видано − холд під незавершені виплати − заявки на вивід + правки (заявки — у резервуючих статусах; правки — ручні корекції адміністратора, поле adjustments).

Відповідь · 200
{
  "merchant_id": 1,
  "balances": [
    {
      "currency": "UAH",
      "balance": 598229.84,
      "settled": 6449109.84,
      "paid_out": 5849865.00,
      "held": 1015.00,
      "withdrawn": 0.00,
      "adjustments": 0.00,
      "turnover": 7010100.00,
      "commission": 560792.16,
      "orders": 438,
      "payouts": 56,
      "withdrawals": 0
    }
  ],
  "generated_at": "2026-08-09T10:15:00+00:00"
}
held - гроші під незавершені виплати

Створена виплата одразу відкладає суму з комісією: вона йде в held і зменшує balance. Виконання переносить її в paid_out, скасування чи протермінування повертає в доступні.

Крипто-виплати USDT

Виплата USDT на адресу отримувача з вашого гарячого гаманця в LumaPay. Гаманець свій у кожного мерчанта й у кожній мережі. Баланс поповнюєте ви: переказ USDT на адресу гаманця зараховується автоматично після підтвердження в мережі. Газ (TRX/BNB) на виплату платимо ми.

Підпис - як у розділі Автентифікація: X-API-Key і X-Signature, ті самі ключі, що й для приймання; для GET підписується порожній рядок. Суми - рядком USDT з крапкою, до 6 знаків після неї ("25", "10.5", "0.000001"), у відповідях завжди 6 знаків ("25.000000"). Мережа: trc20 або bep20.

GET/api/v1/crypto/balance
Відповідь · 200
{
  "wallets": [
    {
      "network": "trc20",
      "address": "T...",
      "status": "active",
      "currency": "USDT",
      "balance": "1000.000000",
      "held": "25.250000",
      "available": "974.750000"
    }
  ]
}

held - утримано під виплати, що ще не завершені; available = balance - held. Виплата створюється, лише якщо available покриває суму разом із комісією. status: disabled - виплати в цій мережі вимкнені.

POST/api/v1/crypto/payouts
Параметри запиту
ПолеТипОпис
order_idstringrequiredВаш ідентифікатор виплати, до 255 символів. Ключ ідемпотентності.
networkstringrequiredtrc20 або bep20.
addressstringrequiredАдреса отримувача: TRON base58 з контрольною сумою (T...) або BSC 0x... (у змішаному регістрі перевіряється контрольна сума EIP-55).
amountstringrequiredСума до отримувача. Комісія LumaPay утримується з вашого балансу зверху.
callback_urlstring(url)optionalАдреса вебхуків цієї виплати; інакше - адреса колбеків виплат мерчанта.
Відповідь · 201 (створено) або 200 (повтор того самого запиту)
{
  "id": "9b1f2c3d-4e5f-6789-abcd-ef0123456789", // наш id, далі всі запити за ним
  "order_id": "wd-100500",
  "status": "aml",
  "currency": "USDT",
  "network": "trc20",
  "address": "TYkxEDfi1fDqmssSM8ZqnCpCQjSp5ZZmqr",
  "amount": "25.000000",
  "commission": "0.250000",
  "amount_total": "25.250000",
  "tx_hash": null,
  "failure_reason": null,
  "created_at": "2026-10-27T06:40:00+00:00",
  "completed_at": null
}
Проти подвійної виплати

Таймаут або помилка мережі при створенні - повторіть запит з тим самим order_id: повернеться наявна виплата, друга не зʼявиться. Поки виплата в sending, sent або manual, нову з іншим order_id на ту саму операцію не створюйте.

GET/api/v1/crypto/payouts/{id}

Та сама структура, що й при створенні. Основний канал - вебхуки, опитування - резерв, не частіше разу на хвилину.

Статуси крипто-виплати
СтатусЩо означаєЩо робити вам
aml / reviewПеревірка адреси отримувача (AML), автоматична або ручнаЧекати
approvalСума вище порога - чекає підтвердження оператораЧекати
queued / sendingУ черзі на підпис і відправкуЧекати
sentТранзакція в мережі, tx_hash заповнено, чекаємо підтвердженьМожна показати хеш отримувачу
completedПереказ підтверджено в мережі, сума списана з балансуВважати виплату виконаною
failedТранзакція не пройшла, кошти не пішли, утримання знято. tx_hash може бути заповнений: транзакція впала в мережі або протермінувалась і в мережу вже не потрапитьМожна створити нову виплату з новим order_id
rejectedВідхилено перевіркою або операторомНе повторювати на ту саму адресу без розбору
cancelledСкасовано вами-
manualСтан відправки неоднозначний, розбирає оператор; кошти утриманоНе створювати нову виплату на ту саму суму, чекати вебхука
POST/api/v1/crypto/payouts/{id}/cancel

Тіло {"timestamp": <unix-час зараз>}, розбіжність до 5 хвилин: без нього підписаний запит можна було б повторити. Скасувати можна лише до відправки, у статусах aml, review, approval, queued. Інакше 409 payout_not_cancellable і поточний стан виплати в полі payout.

Події крипто-виплат

POST на callback_url виплати, підпис і заголовки - як у розділі Вебхуки. Тіло - поле event і та сама структура, що при створенні.

ПодіяКоли
crypto_payout.sentТранзакцію відправлено, є tx_hash
crypto_payout.completedПідтверджено в мережі
crypto_payout.failedНе пройшла, утримання знято
crypto_payout.rejectedВідхилено
crypto_payout.cancelledСкасовано
Остаточний хеш - у completed

Остаточні стани - completed, failed, rejected, cancelled; sent може прийти після completed, якщо доставку повторювали. crypto_payout.sent може прийти повторно: з тим самим tx_hash після ручної перевірки або з іншим, якщо транзакцію перепідписали (TRON - протермінувалась, не потрапивши в мережу; BSC - зависла й замінена новою з тим самим nonce). Дійсний хеш - той, що в crypto_payout.completed: у BSC це може бути й попередній, якщо в блок потрапила саме вона.

Помилки крипто-виплат

Формат той самий: {"error": "<код>", "message": "..."} і додаткові поля, де вони є. Авторизація (401/403) - як у загальній таблиці.

HTTPerrorКоли
422validation_failedПоля запиту, деталі - в details; у скасуванні - немає або протермінований timestamp
422network_not_supportedМережа не trc20/bep20
422invalid_addressАдреса не проходить перевірку мережі або це адреса самого гаманця
422invalid_amountНе число, нуль або більше 6 знаків після крапки
422amount_too_small / amount_too_largeНижче мінімуму (1 USDT за замовчуванням) або вище ліміту на виплату
422daily_limit_exceededВичерпано добовий ліміт мережі; у тілі - daily_limit, used_today
422insufficient_balanceavailable не покриває суму з комісією; у тілі - required, available
422crypto_wallet_not_enabledГаманця в цій мережі немає або він вимкнений
409idempotency_conflictorder_id уже є з іншими network/address/amount
409payout_not_cancellableСкасування після відправки
404payout_not_foundВиплати немає або вона не ваша
503crypto_payouts_disabled / payouts_suspendedВиплати вимкнені на платформі - повторіть пізніше з тим самим order_id

Вебхуки

Ми надсилаємо POST на вашу адресу доставки на ключові переходи — завершення й прострочення заявки, корекцію суми, і зміни виплат — а не буквально на кожну зміну статусу. Відповідайте 2xx швидко; тіло обробляйте ідемпотентно.

Заголовки доставки
Content-Typeapplication/json
X-EventТип події, напр. order.completed — можна роутити обробники, не розбираючи тіло
X-SignatureHMAC-SHA256(<сире тіло>, webhook_secret) — звіряйте до обробки
Перевірка підпису

Рахуйте HMAC над сирими байтами тіла (до JSON-парсингу) і секретом webhook_secret — не api_secret. Порівнюйте константним часом.

Події заявок

Канонічний payload однаковий для всіх подій order.*: незмінний порядок і назви полів.

ПодіяКоли надсилається
order.completedЗаявку успішно закрито — зараховуйте як депозит
order.amount_correctedСуму вже завершеної заявки виправлено. Приходить замість повторного order.completed
order.expiredЗаявку не оплатили вчасно
order.cancelledЗаявку скасовано оператором, холд повернуто. Додаткове поле: reason
order.disputedЗа заявкою відкрито спір, фінальний статус ще не визначено. Додаткове поле: dispute_id
dispute.resolvedРішення за спором, відкритим через API. Payload заявки з поточним status і полем dispute. Гроші за ним не зараховуйте - для цього order.completed або order.amount_corrected. Якщо підтримка змінить рішення, подія прийде ще раз з новим outcome

Ручний повтор із адмінки надсилає той самий payload із додатковим прапорцем "resent": true.

Канонічне тіло · order.completed
{
  "event": "order.completed",
  "order_id": "7af7c1df-0629-42d4-9068-df6500b3acd6", // наш uuid
  "merchant_order_id": "A-1001",                      // ваш номер
  "status": "completed",
  "currency": "UAH",
  "amount": 1500.00,                                  // до оплати (не змінюється)
  "amount_requested": 1500.00,                        // яку ви запросили
  "amount_received": 1500.00,                         // фактично отримана
  "amount_settled": 1470.00,                          // до зарахування (мінус комісія)
  "exchange_rate": 45.58,
  "settlement_currency": "USDT"
}
Ключові суми

Зараховуйте гравцю/клієнту за amount_received (фактично отримана), а amount - сума до оплати, вона ніколи не змінюється. Вона може на кілька гривень відрізнятися від запитаної (amount_requested): провайдер робить суму унікальною, щоб розпізнати платіж. Показуйте платнику саме amount з відповіді на створення заявки. amount_settled — те, що ляже на ваш баланс після комісії.

Розширене тіло · order.amount_corrected
{
  "event": "order.amount_corrected",
  "order_id": "7af7c1df-0629-42d4-9068-df6500b3acd6",
  "merchant_order_id": "A-1001",
  "status": "completed",
  "currency": "UAH",
  "amount": 1500.00,
  "amount_received": 1400.00,
  "amount_settled": 1372.00,
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "correction_id": 42,
  "reason": "partial_payment",
  "amount_original": 1500.00, // сума до корекції
  "amount_corrected": 1400.00 // нова сума
}
Навіщо окрема подія

Якщо суму виправили після того, як заявка вже була completed (і ви вже отримали order.completed), повторний order.completed призвів би до подвійного зарахування. Тому приходить order.amount_corrected — оновіть суму, не зараховуйте вдруге.

Події виплат

ПодіяКоли
payout.processingВиплату взято в роботу
payout.completedВиплату виконано (є tx_receipt)
payout.expiredСтрок виплати вичерпано
payout.cancelledВиплату скасовано
payout.disputedПо виплаті відкрито спір
Тіло вебхука виплати
{
  "event": "payout.completed",
  "payout_id": "b1e2c3d4-5678-90ab-cdef-1234567890ab", // наш uuid
  "order_id": "P-2001",                                // ВАШ зовнішній номер
  "amount": 1000.00,
  "amount_charged": 1015.00,                           // списано з мерчанта
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "status": "completed",
  "tx_receipt": "https://.../api/v1/payouts/b1e2c3d4-5678-90ab-cdef-1234567890ab/receipts/0", // перший чек
  "receipts": ["https://.../api/v1/payouts/b1e2c3d4-5678-90ab-cdef-1234567890ab/receipts/0"],
  "completed_at": "2026-08-09T10:15:00+00:00"
}
Курс заявки

exchange_rate - курс settlement_currency до валюти документа, зафіксований у момент створення заявки чи виплати й далі незмінний. За ним рахуйте свій середній курс. Для старих документів без курсу поле віддається як null.

Семантика order_id відрізняється

У payout-подіях order_id — це ваш зовнішній номер, а uuid виплати лежить у payout_id. У order-подіях навпаки: order_id — це наш uuid, а ваш номер — merchant_order_id. Мапте акуратно.

Доставка & ретраї

Доставка асинхронна через чергу. Успіх — будь-який 2xx. Не-2xx або таймаут (connect 5с / read 15с) плануює повтор із наростаючою затримкою.

Спроба12345+
Затримка30с60с5 хв15 хв1 год

До 5 спроб; після вичерпання колбек позначається failed. Обробляйте ідемпотентно за order_id/payout_id — той самий колбек може прийти повторно.

Коди помилок

Усі помилки — JSON із полем error (машинний код) і message. Робіть switch по error, не по тексту.

HTTPerrorКоли
400empty_bodyПорожнє тіло у POST
400malformed_jsonТіло — не валідний JSON
400content_type_missingНе надіслано Content-Type: application/json
401api_key_missingНемає заголовка X-API-Key
401api_key_invalidКлюч не збігається з жодним мерчантом
401signature_missingНемає заголовка X-Signature
401signature_invalidПідпис не збігся з HMAC тіла
403merchant_suspendedМерчанта призупинено/заблоковано
403ip_not_allowedIP не в білому списку
404order_not_foundЗаявку не знайдено (або чужа)
404payout_not_foundВиплату не знайдено (або чужа)
404unsupported_api_versionШлях без версії або невідома версія
404endpoint_not_foundВерсія є, шляху немає
405method_not_allowedШлях є, але не для цього методу
409idempotency_conflictТой самий order_id зі зміненим тілом
409dispute_not_availableСпір за тестовою заявкою пісочниці не відкривається
422validation_failedПоля не пройшли валідацію (деталі — в details)
422amount_out_of_rangeСума поза min/max (є в тілі)
422currency_not_supportedВалюта не активна на площадці
422currency_not_enabledПриймання цієї валюти не увімкнено для мерчанта
422payout_not_enabledВиплати цієї валюти не увімкнено
422insufficient_balanceБаланс мерчанта разом із лімітом мінуса не покриває суму виплати з комісією. У тілі - required, balance, overdraft_limit
429merchant_limitДенний ліміт суми або кількості
429—Ліміт частоти: 600 запитів за хвилину на ключ. Відповідь стандартна для сервера й поля error не містить — перевіряйте код 429 до розбору тіла. Заголовок Retry-After вказує, скільки чекати
503no_requisite_availableНемає вільного реквізиту (є Retry-After: 5)
503rate_unavailableДля валюти не налаштовано курс - заявку/виплату не створено
503accepting_suspendedKill-switch блокує приймання
503payouts_suspendedKill-switch блокує виплати

LumaPay · Merchant API v1 · усі суми у звичайних одиницях (2 знаки), час у ISO 8601 (UTC). Секрети керуються в кабінеті мерчанта → «Інтеграція».