Все статьи Разработчикам

Webhook-уведомления Ketmus: события, подпись и повторные доставки

Какие события присылает Ketmus, как проверить подпись HMAC-SHA512 и почему ваш сервер должен отвечать 2xx.

Когда вы передаёте callback_url при создании счёта, Ketmus отправляет на этот адрес POST-запрос при каждом изменении статуса. Тело запроса — JSON счёта в том же формате, что и в ответе API, плюс поле event.

События

  • invoice.pending, invoice.confirming, invoice.paid, invoice.paid_over, invoice.wrong_amount, invoice.expired, invoice.cancelled — повторяют смену статуса счёта.
  • invoice.late_payment — перевод пришёл уже после истечения срока счёта.
  • static.payment — поступление на статический адрес клиента; в теле дополнительно customer_id и static_address.
  • webhook.test — тестовое событие, которое можно отправить кнопкой из кабинета, чтобы проверить приёмник.

Заголовки и подпись

Каждый запрос содержит заголовки x-ketmus-event, x-ketmus-timestamp и x-ketmus-signature. Подпись — HMAC-SHA512 от строки «timestamp.body» с секретом проекта из кабинета. Проверяйте её по сырому телу запроса до разбора JSON и отклоняйте события старше 5 минут.

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyKetmus(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  if (!parts.t || !parts.v1 || age > 300) return false
  const expected = createHmac('sha512', secret).update(`${parts.t}.${rawBody}`).digest('hex')
  return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

Для совместимости со старыми интеграциями те же значения дублируются в заголовках x-platupay-*. Новым интеграциям следует читать только x-ketmus-*.

Повторные доставки

Ваш сервер должен ответить любым кодом 2xx в течение 10 секунд. Иначе Ketmus повторит доставку через 1, 5 и 30 минут, затем через 2, 6 и 24 часа. Поэтому обработчик должен быть идемпотентным: одно и то же событие может прийти дважды — сверяйтесь с id счёта и статусом, который вы уже сохранили.

Не доверяйте webhook как единственному источнику истины для выдачи товара: подтверждайте статус запросом GET /api/merchant/v1/invoices/{id} или проверяйте подпись, как показано выше. Любой из этих способов исключает подделку уведомления.

Читайте также