Що таке MonoPay / plata by mono і як виглядає інтеграція
Якщо дуже коротко: ти на своєму бекенді створюєш рахунок (invoice) через API monobank, отримуєш у відповідь invoiceId та pageUrl, відправляєш користувача на оплату, а результат фіксуєш за подією, яку monobank надсилає на твій webHookUrl. Сам monobank прямо рекомендує вважати webhook основним механізмом отримання статусів, а метод перевірки статусу — використовувати лише як “план Б” при розсинхронізації.
Ключові пункти підключення:
-
Підключення еквайрингу у бізнес-кабінеті та отримання токена (X-Token).
-
Створення інвойсу (
/api/merchant/invoice/create) з сумою, описом,redirectUrlтаwebHookUrl. -
Оплата на сторінці
pageUrl(картка/Apple Pay/Google Pay/оплата в застосунку — залежить від сценарію). -
Webhook від monobank про зміну статусу інвойсу.
-
Верифікація підпису webhook (ECDSA, заголовок
X-Sign). -
Підтвердження у своїй системі: змінюєш статус замовлення/підписки/рахунку.
-
Резервна перевірка статусу (
/api/merchant/invoice/status) якщо треба звіритися.
Реєстрація та підключення еквайрингу: що потрібно зробити до написання коду
Для підключення еквайрингу потрібно бути зареєстрованим як ФОП і мати відкритий рахунок ФОП у mono.
Далі — авторизація у web-кабінеті та заповнення інформації про бізнес (вид діяльності, посилання на сайт/соцмережі), після чого ти обираєш і підключаєш платіжні інструменти.
Це важливо: якщо еквайринг не підключений, токена для API у тебе просто не буде (він “стає доступним після підключення еквайрингу”).
У документації еквайрингу є чіткі кроки отримання токена:
-
зайти у web-кабінет;
-
перейти на вкладку потрібного методу прийому оплат і натиснути “Налаштувати”;

- Тепер можна створити токен

Цей токен ти потім передаєш у заголовку X-Token для API-запитів.
Тестове середовище
monobank дає можливість працювати у тестовому середовищі: для цього потрібен тестовий токен, а “карткові” дані можуть бути будь-якими валідними (з валідним номером за алгоритмом Луна) — фінансова авторизація не відбудеться, але інтеграцію ти проганяєш.
Архітектура інтеграції: що зберігати у БД
Щоб інтеграція була контрольована (і щоб потім не розбиратися, чому “гроші прийшли, а замовлення не оплатилось”), я раджу зберігати щонайменше:payments / invoices
-
id(внутрішній) -
order_id(або інший бізнес-ідентифікатор) -
invoice_id(з monobank) -
amount(у мінімальних одиницях, наприклад копійки) -
ccy(ISO 4217, за замовчуванням 980) -
status(created/processing/success/expired/failure тощо) -
created_at,updated_at -
modified_date(з monobank — критично для правильного упорядкування вебхуків) -
raw_last_payload(не обов’язково, але корисно для дебагу)
Чому modifiedDate важливий?
monobank попереджає, що вебхуки не гарантуються “один за одним”, і теоретично status=success може прийти раніше за status=processing. Тому “правильна істина” — це подія з більшим modifiedDate.
Також врахуй повтори: бекенд еквайрингу робить до 3 спроб POST вебхуку, доки не отримає 200 OK. Це означає: твій webhook-handler має бути ідемпотентним (повторне отримання того ж стану не повинно ламати систему).
Створення рахунку (invoice/create): ключові параметри та приклад запиту
Ендпоінт
Офіційний метод створення рахунку:
POST https://api.monobank.ua/api/merchant/invoice/create
Заголовки
-
X-Token: <токен з кабінету> -
опційно:
X-Cms/X-Cms-Version— якщо ти робиш модуль для CMS (корисно, якщо інтеграція пакується як плагін).
Тіло запиту: що мінімально потрібно
Мінімально зазвичай використовують:
-
amount— сума у мінімальних одиницях (для гривні — копійки) -
ccy— 980 за замовчуванням -
merchantPaymInfo— “людська” інформація про платіж:reference,destination,comment, кошикbasketOrder(особливо актуально для e-commerce) -
redirectUrl— куди повернути клієнта після завершення оплати (успіх або помилка) -
webHookUrl— куди monobank надсилатиме статус при зміні (крімexpired) -
validity— строк життя рахунку в секундах (за замовчуванням 24 години) -
paymentType—debitабоhold(якщо потрібен холд з подальшою фіналізацією)
У відповідь ти отримуєш:
-
invoiceId -
pageUrl(посилання на оплату)
Приклад на PHP 8.x (cURL)
$token = getenv('MONO_X_TOKEN');
$payload = [
'amount' => 19900, // 199.00 UAH -> у копійках
'ccy' => 980,
'merchantPaymInfo' => [
'reference' => 'ORDER-100045', // твій унікальний референс
'destination' => 'Оплата замовлення #100045',
'comment' => 'Дякуємо за покупку',
'basketOrder' => [
[
'name' => 'Підписка PRO, 1 місяць',
'qty' => 1,
'sum' => 19900,
'total' => 19900,
'unit' => 'шт.',
'code' => 'SUB-PRO-1M',
],
],
],
'redirectUrl' => 'https://example.com/pay/return',
'webHookUrl' => 'https://example.com/pay/mono/webhook',
'validity' => 3600,
'paymentType' => 'debit',
];
$ch = curl_init('https://api.monobank.ua/api/merchant/invoice/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Token: ' . $token,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new RuntimeException("Monobank create invoice failed: HTTP $httpCode; body=$response");
}
$data = json_decode($response, true);
$invoiceId = $data['invoiceId'];
$pageUrl = $data['pageUrl'];
// 1) зберігаєш invoiceId у БД разом із order_id
// 2) редіректиш клієнта на $pageUrl
header('Location: ' . $pageUrl, true, 302);
exit;
Семантика полів і сам ендпоінт відповідають документації: сума в мінімальних одиницях, redirectUrl, webHookUrl, а відповідь містить invoiceId і pageUrl.
Повернення користувача: redirectUrl
Після оплати користувача перекине на твій redirectUrl (GET). Тут часто роблять сторінки:
-
“Оплата успішна, ми обробляємо замовлення”
-
“Оплата не пройшла, спробуйте ще раз”
Але принципово важливо: не треба підтверджувати оплату лише на основі redirect-факту.
Redirect — це клієнтський UX, який можна перервати, підмінити, зафаєрволити, або користувач може просто закрити вкладку.
На стороні серверу треба оброблювати:
-
webhook зі статусом (з верифікацією підпису), або
-
резервно: запит статуса інвойсу.
Webhook: прийом подій і верифікація підпису (ECDSA, X-Sign)
Як приходять вебхуки
monobank надсилає POST на webHookUrl при зміні статусу (крім expired), тіло запиту ідентичне відповіді методу “Статус рахунку”, а в заголовках є X-Sign — підпис тіла webhook по стандарту ECDSA.
Також важливі дві особливості:
- до 3 спроб доставки до
200 OK; - події можуть прийти не в хронологічному порядку, орієнтуйся на
modifiedDate.
Отримання публічного ключа
У прикладі з документації вказано, що публічний ключ для верифікації потрібно отримати через ендпоінт https://api.monobank.ua/api/merchant/pubkey.
Практичний PHP-приклад верифікації
Нижче — приклад, адаптований під реальний webhook-handler. Логіка:
-
читаємо raw body;
-
беремо
X-Sign; -
верифікуємо через
openssl_verify; -
якщо ок — парсимо JSON і оновлюємо статус у БД з урахуванням
modifiedDate.
function getMonoPubKeyPem(string $token): string
{
$ch = curl_init('https://api.monobank.ua/api/merchant/pubkey');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Token: ' . $token],
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200) {
throw new RuntimeException("Cannot fetch monobank pubkey: HTTP $code; body=$resp");
}
// У документації приклади працюють з base64-представленням PEM.
// Тут припустимо, що API повертає base64-рядок, який треба decode.
return base64_decode(trim($resp));
}
$token = getenv('MONO_X_TOKEN');
$rawBody = file_get_contents('php://input');
$xSign = $_SERVER['HTTP_X_SIGN'] ?? '';
if ($xSign === '') {
http_response_code(400);
echo 'Missing X-Sign';
exit;
}
$signature = base64_decode($xSign);
$pubKeyPem = getMonoPubKeyPem($token);
$pubKey = openssl_get_publickey($pubKeyPem);
if ($pubKey === false) {
http_response_code(500);
echo 'Invalid pubkey';
exit;
}
// OPENSSL_ALGO_SHA256 — як у прикладах документації
$ok = openssl_verify($rawBody, $signature, $pubKey, OPENSSL_ALGO_SHA256);
if ($ok !== 1) {
http_response_code(401);
echo 'Invalid signature';
exit;
}
$payload = json_decode($rawBody, true);
if (!is_array($payload) || empty($payload['invoiceId'])) {
http_response_code(400);
echo 'Bad payload';
exit;
}
$invoiceId = $payload['invoiceId'];
$status = $payload['status'] ?? 'unknown';
$modifiedDate = $payload['modifiedDate'] ?? null;
// Далі — твоя бізнес-логіка:
// 1) знайти запис в БД за invoiceId
// 2) порівняти modifiedDate (не оновлювати старішим payload)
// 3) якщо status=success -> відмітити замовлення як оплачене
// 4) повернути 200 OK, щоб monobank не ретраїв webhook
http_response_code(200);
echo 'OK';
Важливі деталі (вони з документації):
X-Sign— ECDSA-підпис тіла webhook;modifiedDate— головний критерій “який статус актуальний”;- тіло webhook відповідає структурі “статусу рахунку”, де є
invoiceId,status, суми, причини помилки тощо.
Статуси та поля відповіді: що реально використовувати в бізнес-логіці
Метод статусу інвойсу повертає корисний набір полів, серед яких:
-
invoiceId -
status(наприкладcreated) -
failureReasonтаerrCode(коли платіж не пройшов) -
amount,ccy,finalAmount -
createdDate,modifiedDate -
reference,destination -
paymentInfo(маскована картка,rrn,tranId, тощо — корисно для звірок) -
cancelList, інші об’єкти
У прикладі з документації прямо видно ці поля у response sample.
Практична рекомендація:
для більшості e-commerce сценаріїв ти використовуєш як мінімум:
-
status -
invoiceId -
modifiedDate -
finalAmount(якщо в тебе можливі часткові списання/комісії/холди)
А failureReason / errCode — щоб показати людині адекватне повідомлення (“Недостатньо коштів”, “Невірний CVV”, тощо) і не перетворювати оплату на “магію, що не працює”.
Перевірка статусу платежу (invoice/status) як “страховка”
Ендпоінт:
-
GET /api/merchant/invoice/status?invoiceId={invoiceId}з заголовкомX-Token.
monobank прямо каже: не використовуй цей сервіс як основний механізм після проведення операції; краще вебхуки. Але для кейсів “webhook не прийшов” або “на боці продавця був даунтайм” — це правильний інструмент для відновлення консистентності.
Приклад на PHP
$token = getenv('MONO_X_TOKEN');
$invoiceId = $_GET['invoiceId'];
$url = 'https://api.monobank.ua/api/merchant/invoice/status?invoiceId=' . urlencode($invoiceId);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Token: ' . $token],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new RuntimeException("Monobank status failed: HTTP $httpCode; body=$response");
}
$data = json_decode($response, true);
// Далі: звіряєш data['status'], data['modifiedDate'], data['finalAmount'] і оновлюєш БД
Готові модулі та інтеграції: коли краще “не писати код з нуля”
Якщо твій сайт уже працює на популярній CMS/конструкторі, інколи найкраще рішення — не винаходити велосипед, а поставити готову інтеграцію.
Офіційна сторінка готових інтеграцій
monobank має централізовану сторінку, де зібрані готові інтеграції та платіжні модулі для різних систем (“натиснувши на лого партнера, можна перейти на інструкцію з налаштування”).
Це варіант “мінімум кастому”: вставив токен у налаштування — і працює.
Приклад: WooCommerce / WordPress
Є офіційний плагін для WordPress/WooCommerce (Monobank WP Payment Plugin), який позиціонується як офіційний модуль для підключення інтернет-еквайрингу.
Приклад: OpenCart
В екосистемі OpenCart теж є модулі під MonoPay (включно з публічними каталогами/форумами модулів).
Коли модуль — кращий вибір
Обирай модуль, якщо:
-
тобі потрібно “швидко запустити оплату” без кастомної логіки;
-
ти не плануєш складні сценарії (холди, токенізація, спліт-платежі, свій білінг);
-
ти хочеш підтримку оновлень “у рамках CMS”.
Коли API-інтеграція потрібна обов’язково
Пиши свою інтеграцію через API, якщо:
-
у тебе кастомний бекенд (Laravel/CI/Symfony/Node/Go тощо) і нетипова логіка оплат;
-
потрібні складні сценарії (наприклад, холд/фіналізація, токенізація, мульти-продукти з власними правилами);
-
ти хочеш повний контроль: власні ретраї, звірки, аудит подій, антифрод-логіку.
Практичний чек-лист “зробив і забув”: як довести інтеграцію до production-якості
-
Токен зберігай тільки в секретах/ENV (не в коді, не в репозиторії).
-
referenceроби унікальним на твоєму боці (наприклад,ORDER-). Це дуже допомагає у звірках. -
Webhook-endpoint:
-
повинен швидко відповідати
200 OK; -
має бути ідемпотентним;
-
обов’язково верифікує
X-Sign.
-
-
Логіка оновлення статусів:
-
не довіряй порядку приходу подій;
-
завжди порівнюй
modifiedDateі не перезаписуй “новий” статус “старим”.
-
-
Зроби reconciliation job (крон/черга): раз на N хвилин перевіряй “завислі” інвойси через
invoice/status(тільки для тих, у кого давно не було webhook). Це узгоджується з призначенням методу статусу як резервного. -
Перед запуском у прод:
-
прогони тестове середовище з тестовим токеном;
-
перевір, що твої URL доступні з інтернету і не блокуються WAF/Cloudflare;
-
перевір, що “success” реально переводить замовлення в “paid” тільки на сервері (вебхук/статус), а не “по редіректу”.
-
Інтеграція MonoPay (plata by mono) добре лягає на сучасну архітектуру “інвойс → платіжна сторінка → вебхук → підтвердження в БД”, але якість рішення визначають три речі:
-
коректне підключення і керування токеном у бізнес-кабінеті;
-
правильна обробка вебхуків (підпис +
modifiedDate+ ідемпотентність); -
наявність резервного механізму звірки через
invoice/statusдля рідкісних розсинхронізацій.