Повторный вебхук не должен создавать второй платёж, заказ или задачу
Идемпотентность — это не “защита от дублей”, а контракт обработчика: один и тот же запрос с одним ключом должен приводить к одному и тому же эффекту. Если внешний сервис шлёт ретраи, а вы просто делаете INSERT и надеетесь на удачу, дубли неизбежны. Смотрим в тело запроса и ищем стабильный идентификатор события: event_id, message_id, order_id + тип операции.
Храним ключ идемпотентности отдельно от бизнес-таблицы: таблица processed_events с уникальным индексом по key. Сценарий простой: получили webhook → в транзакции пытаемся записать key → если конфликт, отвечаем 200 и выходим → если запись прошла, выполняем бизнес-логику. Для PostgreSQL это обычно выглядит так: INSERT ... ON CONFLICT DO NOTHING; для Redis — SET key value NX EX. Главное не кеш, а атомарная фиксация факта обработки.
Если полезная нагрузка нестабильна, ключ строят из нормализованных полей: например, provider + external_id + action. Не из всего JSON целиком: лишнее поле от партнёра сломает хэш и превратит повтор в “новое” событие. Прокидываем стейт через метаданные: request_id, attempt, source, signature_status — потом это спасает на разборе инцидента.
Ретрай-политика решает всё: 5xx — ретраим, 4xx по валидации — нет, 200 после успешной фиксации — всегда. Идемпотентность — это не роскошь, а база: без неё любой webhook-handle превращается в генератор дублей.
Автоматизация на вебхуках
@webhook_automation_hub_arb
Повторный вебхук не должен создавать второй платёж, заказ или задачу
Этот пост опубликован в Telegram-канале Автоматизация на вебхуках. Подписаться можно по ссылке: @webhook_automation_hub_arb.