Що таке підписані вебхуки Payora
Підписані вебхуки Payora — це HTTP-запити з цифровим підписом, тож ваш PHP-ендпоінт може перевірити, що подія справді надійшла від Payora і не була змінена в дорозі. Якщо ви вивчаєте, як перевірити підписані вебхуки Payora в PHP, базовий процес простий: Payora надсилає подію, ваш сервер читає сирий payload, а ваш код перевіряє підпис, перш ніж довіряти будь-чому в тілі запиту. Саме тому перевірка вебхука Payora PHP має виконуватися до будь-якої бізнес-логіки, а для тих, хто шукає практичний підхід, корисно розуміти і як перевірити HMAC вебхука PHP.
Ця перевірка важлива, бо вебхук може надійти від будь-якого клієнта, який знає вашу URL-адресу. Зловмисник може взяти ендпоінт із логів, а потім надіслати фальшиву подію про оплату з акуратним JSON-тілом. Підпис зупиняє такий трюк.
Саму подію має сенс обробляти лише після перевірки. До цього це просто текст.
Уявіть це як квиток з номером на вході. Тіло — це повідомлення, підпис — штамп, а ваш PHP-код вирішує, чи збігаються обидві частини. Якщо так, ви обробляєте подію. Якщо ні — швидко відхиляєте.
Якщо на сайті у вас уже працює платіжний флоу, та сама дисципліна проявляється й в інших частинах стеку. Той, хто читає гайд про crypto payment gateway for ecommerce, побачить той самий підхід: менше довіряти, більше перевіряти, і лише потім оновлювати замовлення.
Що потрібно для PHP-ендпоінта вебхука
Для цього не потрібен величезний фреймворк. Достатньо PHP 8.x, а для деяких проєктів може підійти й PHP 7.4, але код має мати доступ до сирого тіла запиту та до заголовків, які надсилає Payora. Тобто сервер повинен забезпечувати звичайний доступ до HTTP-запиту, а не через власний шар, що переписує або відкидає заголовки.
Використовуйте HTTPS. Без TLS вебхук іде відкритим текстом, і один лише підпис не виправляє слабкий транспортний захист. Сертифікат, коректна конфігурація віртуального хоста та доступна публічна URL-адреса — це базовий мінімум.
На практиці важливі два PHP-розширення: hash для роботи з HMAC і, якщо ви будуєте обробку JSON навколо цього, вбудовані JSON-функції, що постачаються разом із PHP. Для тестування з вашого боку вам також може знадобитися cURL, але самому ендпоінту вебхука він не потрібен.
Також вам потрібен секрет Payora або signing key, який відповідає конфігурації вебхука у вашому акаунті Payora. Зберігайте це значення поза web root. Не тримайте його в git. Секрет у публічному репозиторії дуже швидко перетворює перевірку на виставу.
І ще одне: ваш ендпоінт має приймати лише POST-запити. GET-запити можуть повертати 405, і така невелика обмежувальна умова економить час під час діагностики, бо звужує коло трафіку, який ви реально перевіряєте.
Захопіть сирий payload вебхука та заголовки
Перевірка підпису залежить від точних байтів, які надіслала Payora. У PHP читайте сире тіло запиту через file_get_contents('php://input'). Не збирайте payload заново з $_POST або з декодованого JSON, бо будь-який із цих кроків може змінити пробіли, порядок ключів або кодування.
Те саме правило стосується й заголовків. Вам потрібен заголовок із підписом. Використовуйте getallheaders(), якщо він доступний, або читайте серверні змінні на кшталт $_SERVER['HTTP_X_...'], якщо ваше середовище прибирає цей helper для заголовків. Невеликі відмінності між хостингами тут мають значення.
Уявіть собі body вебхука на 412 байтів. Якщо ваш код декодує його і потім заново кодує, поля можуть виглядати тими самими, але байти вже не будуть ідентичними. Однієї такої невідповідності достатньо, щоб зламати перевірку.
Ось звичка, яку варто зберегти: спочатку захопити, потім парсити. Спершу збережіть сирий рядок. Потім перевірте заголовки. І лише після цього чіпайте структуру payload.
Якщо ви тестуєте платіжні флоу до запуску, ця сама звичка економить час і в інших місцях. Стаття про як протестувати crypto payment застосовує ту саму логіку до sandbox-подій, де точність payload — це те, що робить тест корисним.
Відтворіть очікуваний підпис у PHP
Зазвичай використовується HMAC, обчислений із сирого payload і спільного секрету. Почніть з алгоритму, який вказано в документації Payora для вебхуків. Якщо там зазначено SHA-256, використовуйте SHA-256. Якщо вказано префікс, версію або формат timestamp, включайте саме цю структуру.
Поширений варіант виглядає так: сформувати підписуване повідомлення з timestamp і сирого тіла, а потім запустити hash_hmac() зі спільним секретом і вказаним hash-алгоритмом. Результат може вимагати hex-кодування або base64-кодування залежно від специфікації. Не здогадуйтеся про формат. Візьміть його з налаштувань вебхука.
Ось мінімальний приклад у стилі PHP, з плейсхолдерами там, де формат Payora може відрізнятися:
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_PAYORA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_PAYORA_SIGNATURE'] ?? '';
$secret = getenv('PAYORA_WEBHOOK_SECRET');
$signedPayload = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $signedPayload, $secret);Цей приклад — лише шаблон, а не гарантія. Якщо Payora підписує лише payload, приберіть timestamp зі строки. Якщо Payora підписує канонічну JSON-форму, дотримуйтеся саме цього методу, а не зручнішого для вас. Один неправильний роздільник зламає кожен запит.
Для мерчантів, яким також важливі правила білінгу та крайні випадки, допис про ліміти рахунків для криптоплатежів буде корисним доповненням, бо обробка вебхуків і політика інвойсів часто перетинаються в одному життєвому циклі замовлення.
Порівняйте обчислений підпис із підписом Payora
Коли очікуваний підпис уже готовий, порівнюйте його з вхідним підписом через порівняння за сталий час. У PHP для цього в більшості випадків використовуйте hash_equals(). Простий == або === тут не підходить, бо вони можуть підказувати часом виконання і гірше захищають від тонких випадків невідповідності.
Перед порівнянням точно нормалізуйте формат так, як очікує Payora. Якщо заголовок у hex — порівнюйте hex. Якщо заголовок у base64 — порівнюйте base64. Якщо вхідний підпис має префікс на кшталт sha256=, приберіть або залиште його саме так, як сказано в документації.
Приклад флоу:
- Зчитати сире тіло.
- Зчитати заголовок із підписом.
- Сформувати очікуваний підпис за документованою формулою.
- Порівняти обидва рядки через
hash_equals(). - Відхилити запит із 401 або 400, якщо вони не збігаються.
401 має сенс, коли підпис неправильний. 400 має сенс, коли відсутні потрібні заголовки. У будь-якому разі відповідь має бути короткою. Без зайвих підказок.
Ось одна фраза, яка тут достатня: не виводьте секрет. Ця помилка трапляється рідко, але наслідки в неї катастрофічні.
Обробляйте timestamp, захист від повторного відтворення та правила допуску
Якщо Payora додає timestamp або nonce, перевірте його до того, як приймати подію. Це блокує replay-атаки, коли зловмисник повторно надсилає дійсний вебхук пізніше й намагається змусити ваш застосунок заплатити двічі або відправити товар двічі. Вікно допустимого часу має відповідати правилу Payora, а не вашому припущенню.
Типовий підхід — порівняти timestamp вебхука з часом вашого сервера і відхилити застарілі запити поза вікном допуску. Якщо в документації сказано 5 хвилин, використовуйте 5 хвилин. Якщо сказано 300 секунд, збережіть 300 секунд як іменоване налаштування і не hardcode-те загадкове число в трьох файлах.
Розсинхрон годинника може ламати хороший трафік. Сервер, який відстає на 7 хвилин, може відхилити коректний запит. Синхронізуйте сервери через NTP і тримайте часовий пояс узгодженим. Часовий пояс не змінює Unix timestamps, але неправильні локальні налаштування все одно ускладнюють читання логів.
Nonce — ще простіший, якщо він є. Збережіть кожен nonce один раз, позначте його як використаний і відмовте в дублікованому запиті. Невеликий кеш або таблиця в базі даних це легко вирішує. Один nonce — одне прийняття.
Якщо ви також приймаєте фіксовану ціну на платіжних сторінках, тут важлива політика. Окремий матеріал про чи варто використовувати fixed price допоможе узгодити правила курсу валют із таймінгом вебхуків, щоб ви не підтвердили неправильну суму після запізнілої події.
Обробіть валідні події та поверніть правильну HTTP-відповідь
Після успішної перевірки розпарсьте payload і розгалузьте обробку за типом події. Подія payment.succeeded не повинна йти тим самим шляхом, що й refund.updated. Розділяйте їх чітко. Так один handler виконує одну задачу, а логи залишаються читабельними.
Бізнес-логіка має відпрацювати до того, як ви надішлете 2xx-відповідь. Якщо ви позначаєте замовлення як оплачене, спершу запишіть рядок у базу. Якщо ставите email у чергу, спершу додайте його до черги. Якщо будь-який із цих кроків не вдається, повертайте 500, і Payora спробує ще раз пізніше.
Такий порядок важливий, бо 2xx-ответ зазвичай означає для Payora, що подію прийнято. Якщо ви повернете 200 занадто рано, а потім впадете на записі в базу, між Payora і вашим застосунком може виникнути розсинхрон. Такі баги коштують дорого о 2-й ночі.
Невеликого body у відповіді достатньо. Підійде “OK”. Ще краще — не повертати body взагалі й залишити лише зрозумілий статус-код. Не echo-те payload назад.
Для команд, які також виставляють інвойси клієнтам через платіжні події, стаття про криптоплатіжний шлюз для фрилансерів показує, як вебхук може перейти від технічної події до реальної білінгової дії без зайвих кроків.
Виправлення помилок під час перевірки підпису
Більшість помилок виникають у п’яти місцях: змінилося сире тіло, неправильна назва заголовка, неправильний секрет, відрізняється кодування або перевірка timestamp виходить за межі допуску. Починайте з body. Якщо ви залогували сирий payload і обчислений підпис, порівняйте саме їх до того, як чіпати базу даних.
Зміна body трапляється часто. Middleware фреймворка може обрізати пробіли, декодувати JSON або нормалізувати кінці рядків. Один зайвий перенос рядка вже достатній. Якщо Payora підписала {"a":1}, а ваш застосунок читає {"a":1}\n, HMAC не збіжиться.
Далі йде невідповідність заголовків. Деякі сервери перетворюють дефіси на підкреслення. Деякі proxy-шари видаляють кастомні заголовки, якщо їх не дозволено явно. Якщо ваш заголовок із підписом не видно в PHP, перевіряйте конфіг вебсервера та налаштування проксі, а не лише код.
Проблеми з кодуванням підступні. UTF-8 і екрановані Unicode-символи можуть чудово виглядати в логах, але все одно давати різні байти. Якщо ваш застосунок переписує JSON, підпис може зламатися, навіть коли payload здається ідентичним у браузері.
Неправильний секрет — найпростіша помилка і та, яку люди перевіряють останньою. Окремо перевірте змінну середовища, staging-секрет і production-секрет. Скопійований секрет з неправильного акаунта може з’їсти пів дня.
Розсинхрон годинника дає інший симптом: підпис збігається, але запит усе одно відхиляється, бо timestamp занадто старий. Якщо це сталося, перевірте NTP, годинники контейнерів і будь-який reverse proxy, який ставить власний час. Невеликий дрейф інфраструктури може виглядати як неправильний підпис.
Якщо ваша інсталяція також пов’язана з WordPress або membership-білінгом, стаття про як приймати USDT payments буде корисною, бо показує, як обробка вебхуків, контроль доступу та підтвердження оплати поєднуються в один потік.
Останній тест дуже допомагає: надішліть той самий payload двічі. Перший запит має пройти. Другий має провалитися, якщо ви зберігаєте і відхиляєте повторно використаний nonce, або пройти лише тоді, коли ваш дизайн вебхука не використовує nonce-захист від повторного відтворення. Цей один дублікат покаже, чи справді працює ваш захист.




Коментарі