МЦРМаркет цифрових рішень
НБУ на 06.11.2026USD 45,4432 ₴▼0,2%EUR 51,0406 ₴▼0,4%PLN 11,6239 ₴▼0,2%GBP 61,2004 ₴▼0,4%CHF 55,6145 ₴▼0,3%CNY 6,814 ₴▼0,1%Конвертер
МЦР

API для розробників

REST API для платіжних посилань з QR-кодом НБУ. Створюйте посилання зі своєї CRM, сайту чи бота, а про оплату дізнавайтеся з вебхука.

Початок роботи

  1. Увійдіть у кабінет → API і створіть ключ. Його покажуть один раз, збережіть його в змінних середовища свого сервера.
  2. Створіть посилання запитом нижче і віддайте клієнту url або shareText.
  3. Додайте адресу вебхука в тому ж розділі кабінету: МЦР повідомить, коли посилання оплачено.

Базова адреса: https://mcr.procrm.ua/api/v1

curl
curl -X POST https://mcr.procrm.ua/api/v1/links \
  -H "Authorization: Bearer $MCR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": { "name": "ТОВ «Приклад»", "iban": "UA213223130000026007233566001", "code": "41234569" },
    "purpose": "Оплата за замовленням 1042",
    "amount": 1250.50,
    "validUntil": "2026-11-30",
    "externalId": "order-1042"
  }'
Node.js
const res = await fetch("https://mcr.procrm.ua/api/v1/links", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MCR_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    recipientId: "6f1c0a52-2a8e-4c4b-9a39-0b3e7f6a1d20",
    purpose: "Оплата за замовленням 1042",
    amount: 1250.5,
    externalId: "order-1042",
  }),
});
const link = await res.json();
if (!res.ok) throw new Error(link.error.message);
// link.url: сторінка оплати з QR-кодом, link.shareText: готовий текст для месенджера

Ключ і ліміти

Кожен запит передає ключ у заголовку Authorization: Bearer mcr_…. Ключ дає доступ до всіх посилань і одержувачів кабінету, у якому його створили. До 10 ключів на кабінет; відкликаний ключ перестає працювати одразу.

Викликайте API лише зі свого сервера. API не відповідає на запити з браузера (немає CORS), тому ключ не можна покласти в код сторінки чи мобільного застосунку. Віджет чату на сайті звертається до вашого бекенду, а бекенд до МЦР.

Ліміт: 60 запитів на хвилину для одного ключа. Кожна відповідь містить заголовки X-RateLimit-Limit і X-RateLimit-Remaining. Понад ліміт приходить 429 із заголовком Retry-After (секунди до нової хвилини).

Формат даних

  • Запити й відповіді в JSON (UTF-8), заголовок Content-Type: application/json, тіло до 64 КБ.
  • Імена полів у camelCase. Невідоме поле в запиті дає помилку 422, тож одруківка не пройде непомітно.
  • Суми: число з двома знаками після крапки (1250.5) або рядок ("1250.50").
  • validUntil: дата YYYY-MM-DD, посилання діє до кінця цього дня за київським часом.
  • Час (createdAt, paidAt): ISO 8601 в UTC.

Помилки

Помилка має HTTP-статус і тіло з кодом і поясненням українською. Для 422 поле fields називає кожне неправильне поле.

{
  "error": {
    "code": "validation_failed",
    "message": "Перевірте поля в fields",
    "fields": {
      "recipient.iban": "Контрольна сума не збігається: перевірте цифри",
      "amount": "Сума має бути більшою за нуль"
    }
  }
}
СтатусcodeКоли
400invalid_jsonТіло не є коректним JSON
401unauthorizedНемає ключа, ключ неправильний або відкликаний
404not_foundПосилання немає у вашому кабінеті
409link_paid, link_cancelledДія не підходить до стану посилання
413too_largeТіло більше за 64 КБ
422validation_failedНеправильні поля, деталі в fields
429rate_limitedПеревищено ліміт запитів
500internalЗбій на боці МЦР, повторіть запит
503rates_unavailableКурс НБУ недоступний, а rate не передано

Створити посилання

POST/links

ПолеТипОпис
recipientobjectОдержувач: { name, iban, code }. Або recipientId
recipientIduuidЗбережений одержувач з GET /recipients замість recipient
purposestringОбов’язкове. Призначення платежу, до 398 символів: МЦР додає до нього « (код оплати …)»
amountnumberСума у валюті currency. Без amount і items суму вводить платник (лише UAH)
itemsarrayЗамість amount: до 50 позицій { name, qty, price }, сума рахується з них
currencystringКод ISO 4217, за замовчуванням UAH
ratenumberДля валюти: гривень за 1 одиницю, до 4 знаків. Без нього береться курс НБУ на сьогодні
markupPercentnumberДля валюти: націнка до курсу, від 0 до 100
validUntildateОстанній день оплати. Без нього посилання діє безстроково
titlestringВаша назва до 100 символів, платник її не бачить
externalIdstringВаш id угоди, замовлення чи чату, до 100 символів, унікальний у кабінеті
metadataobjectДо 20 пар «ключ: значення» (рядки, числа, true/false), повертаються в API і вебхуках

Реквізити перевіряються так само, як у формі на сайті: контрольна сума IBAN і ЄДРПОУ/РНОКПП, довжина полів, обмеження QR-коду НБУ. Для валюти гривні в QR-коді дорівнюють сумі × курс × (1 + націнка / 100), з округленням до копійки.

Відповідь 201 з посиланням. Якщо посилання з таким externalId вже є, приходить 200 з ним без змін. Тож запит безпечно повторити після таймауту: дубля не буде. Для нового посилання до тієї самої угоди передайте новий externalId, наприклад order-1042-2.

Відповідь 201
{
  "id": "0b8e6f1c-5d0a-4f43-9a8e-2f1d3c4b5a69",
  "slug": "k7m2q9xa",
  "url": "https://mcr.procrm.ua/p/k7m2q9xa",
  "pdfUrl": "https://mcr.procrm.ua/p/k7m2q9xa/pdf",
  "status": "active",
  "title": "Замовлення 1042",
  "amount": 1250.5,
  "currency": "UAH",
  "currencyAmount": null,
  "rate": null,
  "rateSource": null,
  "rateDate": null,
  "markupPercent": null,
  "items": [],
  "recipient": { "name": "ТОВ «Приклад»", "iban": "UA213223130000026007233566001", "code": "41234569" },
  "purpose": "Оплата за замовленням 1042 (код оплати k7m2q9xa)",
  "validUntil": "2026-11-30",
  "paidAt": null,
  "externalId": "order-1042",
  "metadata": { "chatId": "tg-55120" },
  "views": 0,
  "shareText": "До сплати: 1 250,50 грн\nОдержувач: ТОВ «Приклад»\n…",
  "createdAt": "2026-10-29T12:00:00.000Z",
  "updatedAt": "2026-10-29T12:00:00.000Z"
}
Валюта, позиції, курс НБУ
{
  "recipientId": "6f1c0a52-2a8e-4c4b-9a39-0b3e7f6a1d20",
  "purpose": "Оплата за рахунком № 77",
  "currency": "EUR",
  "markupPercent": 1.5,
  "items": [
    { "name": "Ліцензія, 12 місяців", "qty": 3, "price": 120 },
    { "name": "Налаштування", "qty": 1, "price": 200 }
  ]
}

Список посилань

GET/links?status=paid&limit=50

ПолеТипОпис
statusstringactive, paid, cancelled або expired
externalIdstringПосилання з цим externalId
limitintegerВід 1 до 100, за замовчуванням 50
cursorstringnextCursor з попередньої відповіді

Новіші першими. nextCursor дорівнює null на останній сторінці.

{ "data": [ { "id": "0b8e6f1c-…", "status": "paid", … } ], "nextCursor": "0b8e6f1c-5d0a-4f43-9a8e-2f1d3c4b5a69" }

Одне посилання

GET/links/{id}

{id}: id або slug. Знайти посилання за своїм id можна через GET /links?externalId=order-1042.

Скасувати

POST/links/{id}/cancel

Без тіла. Сторінка оплати перестає показувати QR-код, вебхук отримує link.cancelled. Уже скасоване повертається без змін, оплачене дає 409 link_paid.

Одержувачі

GET/recipients

Одержувачі, збережені в кабінеті. Їхній id передавайте як recipientId, щоб не надсилати реквізити щоразу.

{ "data": [ { "id": "6f1c0a52-2a8e-4c4b-9a39-0b3e7f6a1d20", "name": "ТОВ «Приклад»", "iban": "UA213223130000026007233566001", "code": "41234569", "createdAt": "2026-10-20T09:00:00.000Z" } ] }

Вебхуки

Адресу додають у кабінеті → API: лише https, до 5 адрес, для кожної свій секрет підпису. Секрет показують один раз; якщо його втрачено, створіть новий там само.

ПолеТипОпис
link.paidподіяПосилання оплачено: за банківською випискою (ПриватБанк, monobank), вручну в кабінеті або через API
link.cancelledподіяПосилання скасовано в кабінеті або через API
link.reopenedподіяПосилання знову активне: власник у кабінеті зняв позначку оплати або відновив скасоване. Платник знову бачить QR-код, paidAt дорівнює null
pingподіяКнопка «Тестовий запит» у кабінеті

МЦР надсилає POST з JSON і такими заголовками:

POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: MCR-Webhooks/1.0
X-MCR-Event: link.paid
X-MCR-Delivery: 9d2f4c1e-7a3b-4e8f-b1c2-5a6d7e8f9a0b
X-MCR-Signature: t=1793275500,v1=5f0c2b…

{
  "id": "3c1e2d4f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
  "type": "link.paid",
  "createdAt": "2026-10-29T12:05:00.000Z",
  "data": { "link": { "id": "0b8e6f1c-…", "status": "paid", "paidAt": "2026-10-29T12:04:31.000Z", "externalId": "order-1042", … } }
}
  • Відповідайте кодом 2xx протягом 8 секунд, довгу обробку робіть після відповіді. Переспрямування (3xx) МЦР не виконує.
  • Без 2xx МЦР повторює запит до 6 разів, приблизно через 1 хв, 5 хв, 30 хв, 2 год, 6 год і 24 год. Після цього доставку позначено «Не доставлено», її можна повторити в кабінеті вручну.
  • Повтор надсилає те саме тіло з тим самим id. Зберігайте оброблені id і пропускайте повтори.
  • Порядок подій не гарантовано: орієнтуйтеся на data.link.status і data.link.updatedAt.
  • Адреси у внутрішніх мережах (localhost, 10.x, 192.168.x тощо) МЦР не викликає.

Перевірка підпису

Заголовок X-MCR-Signature має вигляд t=<час>,v1=<підпис>. t: Unix-час надсилання в секундах. v1: HMAC-SHA256 у hex від рядка t + "." + тіло, ключ: секрет адреси. Беріть тіло точно таким, як воно прийшло, до розбору JSON. Відхиляйте запити, старші за 5 хвилин: так перехоплений запит не вдасться відправити вдруге.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

/** rawBody: тіло запиту як рядок або Buffer, до JSON.parse. */
export function verifyMcrSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2)));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
  const got = Buffer.from(parts.v1 ?? "");
  return got.length === expected.length && timingSafeEqual(got, expected);
}
PHP
function verify_mcr_signature(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $part) {
        [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
        $parts[$key] = $value;
    }
    $t = (int) ($parts['t'] ?? 0);
    if ($t === 0 || abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}

$raw = file_get_contents('php://input');
if (!verify_mcr_signature($raw, $_SERVER['HTTP_X_MCR_SIGNATURE'] ?? '', getenv('MCR_WEBHOOK_SECRET'))) {
    http_response_code(400);
    exit;
}
$event = json_decode($raw, true);
http_response_code(200);
Python
import hashlib, hmac, time

def verify_mcr_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    try:
        t = int(parts.get("t", ""))
    except ValueError:
        return False
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

OpenAPI

Опис API у форматі OpenAPI 3.1: https://mcr.procrm.ua/api/v1/openapi.json. Його можна імпортувати в Postman чи Insomnia або згенерувати з нього клієнт для своєї мови.