Ketmus
Esc

Уведомления

Проверка подписи

Как убедиться, что вебхук отправлен Ketmus и не был изменён.

Алгоритм

  1. 1

    Получите сырое тело

    Возьмите тело запроса в исходном виде — до разбора JSON. Любое переформатирование сломает подпись.

  2. 2

    Разберите заголовок

    Из x-ketmus-signature вида t=<timestamp>,v1=<signature> возьмите t и v1.

  3. 3

    Вычислите HMAC

    Посчитайте HMAC-SHA512 от строки <t>.<сырое тело> секретом вебхуков магазина, результат — в hex.

  4. 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)
}

Сырое тело во фреймворках

ФреймворкКак получить сырое тело
Expressexpress.raw({ type: 'application/json' }) на маршруте вебхука, тело — в req.body
Next.js (App Router)await request.text() в Route Handler
FastAPIawait request.body()
Djangorequest.body
Laravel$request->getContent()
PHPfile_get_contents('php://input')

Не пропускайте проверку

Без проверки подписи злоумышленник может отправить на ваш URL поддельное событие invoice.paid и получить товар бесплатно.