PrompTom
Все скилы

Приём вебхуков без двойных списаний

webhook-handlerFREEвебхукиплатежи

Подпись, повторы и идемпотентность — четыре правила, из-за которых один платёж не продлевает доступ дважды.

Что делает

  • Срабатывает на «добавь обработчик уведомлений», «подключи платежи», «пришло дважды».
  • Исходит из того, что отправитель пришлёт одно и то же событие несколько раз, не по порядку и иногда сильно позже.
  • Требует проверять подпись по сырым байтам, а не по разобранному и заново собранному JSON: пересборка меняет порядок ключей, и подпись перестаёт сходиться по причине, которую ищут неделю.
  • Хранит идентификатор события с уникальным ограничением и делает проверку и работу одной транзакцией — двумя отдельными запросами они гонятся, а под повторами гонятся обязательно.
  • Отвечает двухсотым как можно раньше и работает после: работа до ответа съедает таймаут отправителя, а таймаут — это ещё один повтор.
  • Требует отвечать двухсотым и на события, которые вы не обрабатываете: четырёхсотый заставляет отправителя повторять их вечно и в итоге отключить адрес.
  • В логах при неудачной подписи оставляет только идентификатор события: ни тела, ни заголовка, ни вычисленной подписи.

Зачем он нужен

Уведомление приходит на каждую смену статуса, и провайдер повторяет его, пока не увидит успешный ответ. Без ключа по идентификатору события один платёж продлевает подписку дважды, а человек об этом даже не узнает — узнает бухгалтерия через месяц. Проверка подписи по пересобранному JSON и работа до ответа — две другие ошибки, которые повторяют почти все.

Куда положить

  1. 1Создайте в проекте папку .claude/skills/webhook-handler
  2. 2Положите в неё файл SKILL.md с текстом ниже
  3. 3Всё. Claude Code подключит скил сам, когда задача подойдёт под описание

Чтобы скил работал во всех проектах, а не в одном, положите его в ~/.claude/skills вместо папки проекта.

Файл SKILL.md

---
name: webhook-handler
description: Receive webhooks from a payment provider or external service correctly. Use when the user adds a callback endpoint, integrates payments, or reports that something was processed twice.
---

# Receiving a webhook

The sender will deliver the same event more than once, out of order, and
sometimes months late. It will retry until you answer 200. Everything
below follows from that.

## The four rules

**1. Verify the signature before reading the body.**
Compute it over the raw bytes, not over the parsed and re-serialised
JSON — reserialising changes key order and whitespace, and the signature
stops matching for reasons nobody finds quickly. Compare in constant
time.

An unsigned endpoint that grants access is a free access endpoint for
anyone who reads your docs.

**2. Be idempotent.**
Store the provider's event id, unique-constrained, and check it before
acting. Do the check and the work in one transaction — two separate
statements race, and under retries they will race, because retries
arrive in bursts.

This is what stops one payment extending a subscription twice.

**3. Answer fast, work after.**
Return 200 as soon as the event is stored. Work done before the
response counts against the sender's timeout, and a timeout means a
retry, which means the same work again.

**4. Answer 200 for events you ignore.**
A 4xx to an event type you do not handle makes the sender retry it
forever and eventually disable the endpoint.

## What to log and what never to log

Log the event id, the type and the outcome. On a failed signature check,
log the event id and nothing else — not the body, not the header, not
the computed digest. Those end up in a log aggregator that more people
can read than you think.

Never log the secret. Never accept it from a query parameter.

## Test it

- Send the same event twice and check the effect happened once.
- Send it with a wrong signature and check nothing happened.
- Send an unknown event type and check the answer is 200.
- Send an event for an object that does not exist locally and check the
  handler does not crash the endpoint for every subsequent event.

## Before finishing

State plainly what happens if the endpoint is down for an hour. If the
answer is "those events are lost", say it — most providers retry for
days, but only if you answered with an error rather than a 200.
Приём вебхуков без двойных списаний — PrompTom