Як працює перевірка підпису вебхука
Підпис вебхука — це короткий доказ того, що подія надійшла від платіжного провайдера, а не від випадкового POST-запиту. Провайдер підписує payload спільним секретом або приватним ключем, а ваша програма перевіряє цей підпис, перш ніж довіряти події. Якщо перевірка не проходить, найкраще реагувати кодом 401 або 400 і не оновлювати замовлення. Іноді саме тому система може не проходить підпис вебхука, навіть коли на перший погляд усе виглядає коректно.
Звучить просто. На практиці — рідко.
У реальних умовах перевірка підпису захищає від підроблених інвойсів, фальшивих повернень коштів і змінених payload’ів, які на перший погляд виглядають цілком легітимно. Вебхук із повідомленням «payment completed» не має жодної цінності, якщо його може підробити будь-хто. Для команд, які використовують посібник із криптоплатіжного шлюзу для ecommerce, ця перевірка стоїть просто між прийняттям платежу та виконанням замовлення, тож одна помилкова припущена деталь може зупинити весь процес.
Більшість провайдерів підписують або весь сирий body, або timestamp, або й те, і інше. Деякі використовують HMAC-SHA256, інші — версіонований заголовок із кількома значеннями підпису. Достатньо однієї невідповідності. Провайдер може бути правий, а ваша програма все одно відхилить подію, якщо ви змінили хоча б один байт.
Спершу переконайтеся, що збій справді пов’язаний із підписом
Логи мають дати відповідь менш ніж за 5 хвилин. Якщо ні — вони недостатньо детальні.
Почніть із точного коду відповіді та тексту помилки. 400 із повідомленням «invalid signature» вказує в один бік; 500 або таймаут — в інший. Збої доставки, неправильні маршрути, TLS-помилки та таймаути проксі можуть виглядати як проблеми з вебхуком у панелі провайдера, але це не одне й те саме. Якщо провайдер показує статус «delivered», а ваш endpoint повертає 200, то перевірка підпису, ймовірно, не є причиною збою.
Перевірте три речі разом: журнал доставки провайдера, журнал вашого застосунку та журнал reverse proxy, якщо він є. Запит, який так і не дійшов до вашого коду, не може провалити перевірку підпису. Запит, що дійшов до коду, але повернув 401, зазвичай може. Одна деталь тут важлива: якщо ви бачите подію в логах, але не бачите помилки підпису, ваш код може відхиляти payload пізніше — уже після етапу перевірки.
Коротко: спершу підтвердьте симптом, а вже потім змінюйте код.
Перевірте спільний секрет або ключ підпису
Це найпоширеніша помилка, і вона настільки банальна, що стає небезпечною. Вебхук-секрет у вашому застосунку має точно збігатися з поточним секретом у платіжній панелі, включно з будь-якими прихованими пробілами, скопійованими з текстового поля. Якщо провайдер змінив секрет минулого вівторка, старий ключ усе ще може лежати у вашому оточенні, доки хтось не оновить його.
Порівнюйте секрет у панелі з тим, що розгорнуто в середовищі, а не лише на своєму ноутбуці. Production, staging і local development мають мати окремі значення. Команда, яка копіює тестовий секрет у production, може витратити 2 години на пошук збою підпису, спричиненого одним вставленим рядком. Такі проблеми часто трапляються під час запуску та під час міграції з одного процесора на інший. Для команд, які користуються посібником із криптоплатіжного шлюзу для ecommerce, невідповідність секретів часто з’являється одразу після релізу нового checkout.
Зберігайте старі ключі лише якщо провайдер підтримує ротацію ключів із перекриттям. Якщо старий і новий ключ одночасно дійсні протягом короткого вікна, ваш перевіряльник має спочатку спробувати активний ключ, а потім попередній. Якщо перекриття не підтримується, приберіть старий ключ з усіх середовищ. Без здогадок.
Відтворіть сире тіло запиту точно так, як його отримано
Саме тут багато хороших реалізацій зазнають невдачі. Перевірка підпису зазвичай залежить від точних сирих байтів, які надіслав провайдер, а не від JSON-об’єкта, який створив ваш фреймворк після парсингу. Якщо код перетворює числа, змінює порядок полів, прибирає пробіли або нормалізує кінці рядків, обчислений підпис може змінитися, навіть якщо payload «виглядає» ідентично.
Деякі фреймворки читають body лише один раз, а потім викидають початковий потік. Інші автоматично парсять JSON ще до того, як спрацює ваш middleware. Це означає, що перевіряльник отримує відтворений об’єкт, а не сире тіло. Зазвичай виправлення полягає в тому, щоб спочатку зберегти незмінений payload, а потім передати саме цей буфер у функцію перевірки підпису. Один параметр фреймворка може визначати, чи буде body збережено, чи переписано.
Наприклад, payload із 14 полями може прийти в певному порядку байтів, якого ваш код ніколи не побачить, якщо парсер відсортує ключі. Кінцевий newline має значення. Один перетворений UTF-8 символ теж має значення. Так, один невидимий символ може зламати всю перевірку.
Якщо ваш стек це підтримує, логуйтe довжину body та хеш сирого body запиту до парсингу. Не логуйтe секрети. Але фіксуйте точний шлях коду, який читає потік. Це значно полегшить пошук причини, чому як протестувати криптоплатіж іноді проходить у Postman, але падає під час реального вебхука від провайдера.
Перевірте timestamp, синхронізацію годинника та захист від повторного відтворення
Багато схем вебхуків містять timestamp, щоб старі події не можна було відтворити пізніше. Ваш перевіряльник може відхилити запит, якщо timestamp виходить за межі допустимого вікна — часто 5 хвилин або іншого ліміту, визначеного провайдером. Це захищає від replay-атак, але водночас означає, що системний час на сервері має бути точним.
Перевірте системний час на кожному сервері, який обробляє вебхуки. Якщо годинник відстає на 3 хвилини, підпис може не пройти навіть тоді, коли payload правильний. Якщо сервер працює в контейнері, перевірте й час на хості. На машині, яка приймає рішення про перевірку, має бути активний NTP або інша служба синхронізації часу. Сервер, який відстає на 10 хвилин, може перетворити валідний вебхук на хибний збій.
Захист від повторного відтворення також має значення. Якщо ваш код кешує вже бачені підписи або event ID, переконайтеся, що TTL кешу відповідає очікуваному вікну повторного відтворення від провайдера. Прострочений запис у кеші може змусити відхилити коректну повторну доставку. Одна повторна спроба від провайдера — це не те саме, що зловмисний replay, тож код має розуміти обидва випадки. Це невелика деталь із великими наслідками.
Переконайтеся, що заголовок підпису розбирається правильно
Заголовки підпису не всі мають однакову структуру. Деякі провайдери надсилають один заголовок із одним хешем, тоді як інші — список пар ключ-значення через кому, мітку версії та timestamp. Ваш парсер має відповідати саме цьому формату. Якщо він очікує v1=, а провайдер надсилає t= разом із кількома версіями, перевірка провалиться ще до початку криптографії.
Стежте за помилками обрізання пробілів. Початкові або кінцеві пробіли мають значення. Так само як і помилки кодування, коли фреймворк декодує заголовки як Latin-1 замість UTF-8. Зайвий newline, випадково вставлений у змінну середовища, може зіпсувати порівняння. Не нормалізуйте заголовок, якщо провайдер прямо не дозволяє це робити. Не переводьте значення в lowercase, якщо воно має залишатися безпечним для бінарних даних. Ці дві дії завдають більше шкоди, ніж мали б.
Кілька схем теж потребують уважного поводження. Деякі провайдери публікують поточний підпис і попередній для перехідних періодів. Ваш перевіряльник має знати, яку версію він використовує, і коректно відхиляти невідомі версії. Якщо заголовок містить і timestamp, і підпис, перевіряйте обидва. Якщо документація провайдера вказує префікс підпису, використовуйте його саме так, як написано. Для більшої інформації про безпечну роботу з вебхуками дивіться найкращі практики безпеки криптоплатежів.
Перевірте на відомому коректному вебхуку
Відомо коректна подія економить час. Надішліть її повторно з панелі провайдера або скористайтеся документованим прикладом payload і порівняйте очікуваний підпис із тим, який обчислює ваш код. Якщо приклад проходить перевірку, а живий трафік — ні, проблема, ймовірно, у конфігурації, середовищі або ротації секрету. Якщо не проходить нічого, проблема у шляху коду.
Використовуйте один event ID і один timestamp. Потім порівняйте чотири речі: сире тіло, значення заголовка, секрет і годинник. Цього достатньо, щоб ізолювати більшість збоїв. Тестовий payload, скопійований у локальний файл, може містити форматування, яке ваш реальний endpoint ніколи не побачить, тому завжди краще користуватися інструментом повторної відправки від провайдера, якщо він є. Одна повторна відправка краща за 20 здогадок.
Якщо провайдер надає калькулятор підпису, проганяйте той самий payload через нього. Якщо ваш результат відрізняється хоча б на один символ, у вас проблема з парсингом або збереженням body. Якщо результат збігається, але провайдер усе одно відхиляє запит, неправильно налаштовані секрет або парсинг заголовка. Цей поділ підказує, де шукати далі. Саме тут доречно зрозуміти, як перевірити вебхук підпис у реальних умовах, а не лише в тестах.
Це також момент, коли staging і production мають бути чітко розділені. Подія зі staging, надіслана до production-секрету, завжди завершиться помилкою, і виглядатиме вона так само, як зламаний підпис, доки ви не перевірите мітку середовища.
Зміцніть і контролюйте endpoint вебхука
Коли перевірка підпису вже працює, важливо зберегти її працездатність. Логуйте event ID, результат перевірки, назву провайдера та назву середовища. Не логуйтe весь секрет. Не логуйтe й повне сире тіло, якщо воно містить чутливі дані клієнта. Використовуйте коротке, зрозуміле повідомлення про помилку для операторів і просте відхилення для провайдера.
Ротуйте webhook-секрет за графіком і документуйте процес передачі. Безпечніше виконувати ротацію у 2 кроки: додати новий ключ, підтвердити, що обидва ключі працюють, якщо підтримується overlap, а потім видалити старий. Розділяйте тестові й production endpoint’и, щоб помилка в staging не торкнулася живих замовлень. Це звучить очевидно; команди все одно їх плутають.
Створюйте alert не на один невдалий запит, а на серію помилок підпису. Одна помилка може бути невдалим retry. П’ять помилок за 1 хвилину вже заслуговують на увагу. Додавайте до alert маршрут, провайдера та код відповіді, щоб черговий інженер міг зрозуміти, чи це невідповідність секрету, баг парсера, чи проблема з часом. Якщо ви ще налаштовуєте endpoint, стаття як налаштувати криптоплатежі допоможе одразу зробити шлях вебхука простішим.
І останнє: тримайте короткий runbook із точними кроками, як виправити збій підпису вебхука для криптоплатежу, включно з тим, де зберігається секрет, як повторно надіслати одну подію та який рядок логів підтверджує успішну перевірку. Runbook на 10 рядків економить годину о 2-й ночі.




Коментарі