Obriym CRMObriym CRMCustomer flow workspace
AIМожливостіЦіниRoadmapІнтеграціїБлог
УвійтиПочати
AIМожливостіЦіниRoadmapІнтеграціїБлог
Блог
Розбори16 серпня 2026 р.

Вебхук Monobank Acquiring: перевірка підпису ECDSA і чому сире тіло запиту має значення

Monobank підписує вебхуки ECDSA, а не спільним HMAC-секретом. Розбираємо перевірку підпису, кешування публічного ключа й дві помилки, після яких перевірка «працює», але нічого не захищає.

Більшість українських платіжних шлюзів підписують callback спільним секретом: береш свій пароль мерчанта, складаєш рядок за формулою, хешуєш — і порівнюєш. Monobank робить інакше, і різницю легко пропустити з наслідками.

Monobank підписує вебхук асиметрично: приватним ключем підписує він, а ви перевіряєте його ПУБЛІЧНИМ ключем, який отримуєте окремим запитом. Спільного секрету немає взагалі. Це строго краще з погляду безпеки — навіть повний злив вашої бази не дає можливості підробити подію, — але вимагає двох речей, яких HMAC-схема не вимагає: правильної роботи з сирим тілом запиту й кешування публічного ключа.

Контракт

Підпис
Заголовок x-sign — ECDSA поверх SHA-256, у base64
Ключ перевірки
Публічний ключ мерчанта, GET /api/merchant/pubkey (потрібен ваш X-Token)
Що підписано
Точні байти тіла запиту — не результат його розбору й повторної серіалізації
Куди приходить
Адреса, яку ви передали в webHookUrl при створенні рахунку. Глобального вебхука мерчанта офіційний API не описує.
Привʼязка до замовлення
merchantPaymInfo.reference — туди ваш магазин кладе власний ідентифікатор замовлення
Суми
У копійках (мінорних одиницях) — не в гривнях
Валюта
Числовий код ISO-4217: 980 — гривня, 840 — долар, 978 — євро
Статус expired
Вебхуком НЕ надсилається — його відстежує та сторона, яка створила рахунок

Помилка перша: розібрати JSON до перевірки підпису

Підпис накладено на конкретну послідовність байтів. Якщо ваш фреймворк автоматично розбирає тіло в обʼєкт, а ви потім серіалізуєте його назад, щоб перевірити підпис, — ви перевіряєте вже інші байти. Порядок ключів, пробіли, представлення чисел, екранування не-ASCII: будь-що з цього може відрізнятися, і воно відрізняється.

Найгірший сценарій тут не «нічого не працює», а «працює на тестових даних і падає на реальних»: простий payload після round-trip випадково збігається, а той, у якому є українські символи в імені покупця або дробова сума, — вже ні.

Правило одне: прочитати тіло як сирий текст, перевірити підпис на ньому, і лише потім розбирати. У Next.js Route Handler це `await request.text()` перед будь-яким `request.json()` — другий виклик на тому самому запиті вже недоступний, тож порядок тут не стилістичний.

Помилка друга: тягнути публічний ключ на кожен вебхук

Публічний ключ мерчанта змінюється рідко, а запит по нього коштує роундтрипу до Monobank і витрачає ваш ліміт. Робити його на кожну оплату — гарантований спосіб отримати збої під навантаженням саме тоді, коли оплат багато.

Але і кешувати назавжди не можна: ключ усе ж може бути перевипущений, і тоді жорсткий кеш означає, що ви перестаєте приймати оплати до наступного деплою.

Робоча схема — кеш плюс оновлення при збої: беремо ключ із кешу, перевіряємо підпис; якщо перевірка не пройшла — один раз перечитуємо ключ і перевіряємо ще раз; якщо не пройшла й тепер — відхиляємо. Так ротація ключа лікується сама, а зловмисник із неправильним підписом коштує вам щонайбільше одного додаткового запиту.

Окремо варто сказати очевидне, що часто роблять неправильно: публічний ключ — це НЕ секрет. Його не треба шифрувати в базі. Він потрібен лише щоб перевіряти підписи, і його знання нікому нічого не дає. Шифрувати належить X-Token, яким ви цей ключ отримуєте.

Перевірка, яка справді захищає

  • Перевірка провалилася — відхиляємо запит. Не логуємо й не пропускаємо далі «на всяк випадок»: вебхук без валідного підпису це не подія, а невідомий, який каже, що вам заплатили.
  • Ключ не вдалося отримати або розшифрувати X-Token — теж відхиляємо. Fail-closed: недоступність перевірки не є підставою довіряти неперевіреному.
  • Порівнюйте також id мерчанта з конфігурації адаптера — так подія від чужого акаунта не потрапить у ваш простір навіть за валідного підпису.
  • Запис оплати робіть ідемпотентним за id рахунку: банк має право доставити ту саму подію двічі, і це нормальна поведінка, а не збій.
  • Не зберігайте номер картки й CVV. Для звірки достатньо суми, валюти, статусу й id рахунку — а те, чого немає в базі, не може витекти.

Куди подіти оплату, яка не знайшла замовлення

Підпис перевірено, сума є, а reference не збігається ні з чим. Найпоширеніша реакція — відкинути подію: 200 у відповідь, нічого не записано. Це найгірше з можливих рішень, бо гроші вже прийшли, і тепер їхнього сліду в системі немає.

Практика, яку варто закладати одразу: зберігати таку оплату як окремий запис без привʼязки до замовлення й показувати її в журналі оплат зі станом «не привʼязано». Менеджер відкриває, бачить суму й покупця і або чіпляє її до наявного замовлення, або створює з неї нове. Це десять хвилин роботи в моделі даних, які прибирають цілий клас звернень «ми заплатили, а у вас не видно».

Підписаний callback автентифікує себе сам

Спокуса вимагати «спершу пройдіть тестове підключення, потім приймайте оплати» зрозуміла, але тут вона зайва: коректний ECDSA-підпис і збіг id мерчанта — це вже повна автентифікація події. Тест зʼєднання корисний як діагностика («який акаунт за цим токеном?»), але робити його умовою прийому реальної оплати означає ставити перешкоду між клієнтом і його грошима.

Часті запитання

Чим підпис Monobank відрізняється від LiqPay і WayForPay?

LiqPay і WayForPay використовують спільний секрет: SHA-1 і HMAC-MD5 відповідно, за формулою складання рядка. Monobank використовує асиметричний ECDSA — спільного секрету немає, ви перевіряєте підпис публічним ключем. Тому копіювати схему одного провайдера на іншого не можна: контракт кожного треба брати з його офіційної документації.

Чи можна налаштувати один вебхук на весь акаунт мерчанта?

Офіційний API такого не описує. Адреса передається в полі webHookUrl при створенні кожного рахунку, тож саме ваш магазин відповідає за те, щоб її туди підставляти.

Чому не приходить статус expired?

Monobank не надсилає його вебхуком. Якщо вам важливо знати про рахунки, яких так і не оплатили, їх стан має відстежувати та сторона, яка ці рахунки створювала.

Суми приходять у гривнях?

Ні, у копійках. 149.90 грн приходить як 14990. Валюта — числовим кодом ISO-4217, а не рядком «UAH».

Чи може CRM зробити повернення коштів через цю інтеграцію?

У нашій реалізації — ні, і це свідомо. Адаптер приймає статуси оплат; створення й скасування рахунків, повернення та будь-які списання лишаються на боці Monobank і магазину. Інтеграція, яка вміє списувати гроші, потребує іншого рівня довіри й іншого аудиту.

Готова інтеграція замість власної

Obriym CRM приймає підписані вебхуки Monobank, LiqPay, WayForPay і Hutko в один журнал оплат — з ідемпотентністю, звіркою із замовленням і сповіщенням команді.

Спробувати безкоштовно

Згадані інтеграції

Monobank

Beta

Покупець оплатив рахунок — CRM отримує підписане підтвердження від Monobank, ставить оплату на замовлення й одразу повідомляє команду. Без зведення виписки руками.

Детальніше

LiqPay

Beta

Платіж, підтверджений LiqPay, дає одне сповіщення команді й оновлює відповідне замовлення в CRM.

Детальніше

WayForPay

Доступно

CRM перевіряє результат оплати, сповіщає команду та привʼязує платіж до наявного замовлення. Якщо збігу немає, оплата залишається у зрозумілому списку для звірки.

Детальніше

Hutko

Доступно

Після кожної оплати CRM фіксує результат, сповіщає команду та прикріплює платіж до наявного замовлення або залишає його у зрозумілому списку для звірки.

Детальніше

Beta: Працює, можна підключати. Ще не перевірено на живому акаунті — трапляються шорсткості, і ми швидко їх правимо.

Obriym CRMObriym CRMCustomer flow workspace

Obriym CRM - сфокусований workspace для sales команд і e-commerce операцій. Ліди, угоди, замовлення та customer retention в одному місці.

Продукт OBRIYM

  • crm@obriym.com
  • OBRIYM
  • Serhii Oberemchuk

ФОП Оберемчук Сергій Олександрович · ЄДР 178752761226

Продукт

  • AI
  • Можливості
  • Ціни
  • Порівняння
  • Блог
  • Roadmap
  • Інтеграції
  • API Reference

Компанія

  • Про OBRIYM
  • Засновник
  • Контакти
  • Roadmap

Розробникам

  • Для розробників
  • OpenAPI spec
  • JS Widget
  • Lead intake API
  • Orders API

©2026Obriym CRM від OBRIYM. Для sales команд і e-commerce операцій.

Умови використанняУмови надання послугОплата та поверненняПолітика конфіденційностіСубпроцесориОбробка данихРеквізитиВидалення данихЦіниAPI Docs