Уведомления
Проверка подписи
Как убедиться, что вебхук отправлен Ketmus и не был изменён.
Алгоритм
- 1
Получите сырое тело
Возьмите тело запроса в исходном виде — до разбора JSON. Любое переформатирование сломает подпись.
- 2
Разберите заголовок
Из
x-ketmus-signatureвидаt=<timestamp>,v1=<signature>возьмитеtиv1. - 3
Вычислите HMAC
Посчитайте HMAC-SHA512 от строки
<t>.<сырое тело>секретом вебхуков магазина, результат — в hex. - 4
Сравните
Сравните результат с
v1функцией постоянного времени и проверьте, чтоtне старше 5 минут.
Примеры
import crypto from 'node:crypto'
/**
* rawBody — тело запроса в исходном виде (строка или Buffer), до JSON.parse.
* header — значение заголовка x-ketmus-signature.
*/
export function verifyKetmusSignature(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=')))
const timestamp = Number(parts.t)
if (!timestamp || !parts.v1) return false
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false
const expected = crypto.createHmac('sha512', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(parts.v1)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Сырое тело во фреймворках
| Фреймворк | Как получить сырое тело |
|---|---|
| Express | express.raw({ type: 'application/json' }) на маршруте вебхука, тело — в req.body |
| Next.js (App Router) | await request.text() в Route Handler |
| FastAPI | await request.body() |
| Django | request.body |
| Laravel | $request->getContent() |
| PHP | file_get_contents('php://input') |
Не пропускайте проверку
Без проверки подписи злоумышленник может отправить на ваш URL поддельное событие invoice.paid и получить товар бесплатно.