channel_name: Автоматизация на вебхуках

content:
Идемпотентность при повторных вебхуках — это не опция, а страховка от дублей

Повторный запрос — норма: сеть моргнула, таймаут на стороне клиента, ретрай у провайдера, ручной повтор из админки. Если хендлер не умеет распознавать дубль, он создает второй платеж, вторую заявку или второй инцидент. Смотрим в тело запроса и ищем стабильный ключ события: event_id, order_id, delivery_id, transaction_id.

Базовая схема простая:
— на входе вычисляем idempotency key;
— кладем его в хранилище с уникальным индексом;
— если ключ уже есть — не выполняем бизнес-операцию, а возвращаем 200/202;
— если операция еще в работе — отдаем тот же результат или честный 409, без магии.
Идемпотентность — это не роскошь, а база. Важно хранить не только факт приема, но и состояние: received, processing, done, failed.

Не полагайтесь на хэш всей полезной нагрузки: внешняя система любит менять порядок полей, добавлять мусор и переименовывать ключи. Лучше нормализовать вход и брать устойчивые поля. Для Postgres обычно хватает unique constraint:

CREATE UNIQUE INDEX ux_webhook_events_key ON webhook_events(idempotency_key);

А дальше — транзакция, запись в outbox и отдельный воркер. Так вы не зависите от того, успели ли отправить письмо, дернуть API или положить задачу в очередь.

Ретрай-политика решает всё: если при 500 вы не уверены, что событие не применилось, значит обработчик должен быть идемпотентным по умолчанию. Иначе ловим 5xx на ровном месте и плодим побочные эффекты. Финальная проверка простая: один и тот же запрос можно прислать три раза, а состояние системы меняется ровно один раз.


network: arbnet

channel_slug: webhook-automation-hub

channel_name: Автоматизация на вебхуках

format: evergreen

content:
API Gateway не должен «умнеть»: он должен безопасно резать JSON на лету

Если шлюз начинает переписывать полезную нагрузку, у вас уже не прокси, а мини-оркестратор. Это допустимо, но только для простых и детерминированных трансформаций: выкинуть лишние поля, переименовать ключи, добавить correlation_id, привести типы. Всё, что зависит от внешних вызовов, состояния или бизнес-логики, выносите в отдельный хендлер.

Базовый набор правил:
— Смотрим в тело запроса и валидируем схему до трансформации.
— Не мутируем исходный JSON «в месте»; создаём новый payload.
— Сохраняем оригинал в логах или в shadow-copy для дебага.
— Прокидываем стейт через метаданные, а не через магию в заголовках.
— Любая трансформация должна быть идемпотентной: повторный запрос обязан дать тот же результат.

Если gateway режет поля для разных downstream-сервисов, вводите явные mapping-таблицы. Пример: один входной payload превращается в несколько контрактов, но правило маппинга лежит в конфиге, а не в коде без тестов:

{
  "drop": ["debug", "trace"],
  "rename": {"userId": "customer_id"},
  "defaults": {"source": "gateway"}
}

Самая частая ошибка — попытка «починить» кривой API на лету и тихо проглотить 5xx. Не глотать: ставьте timeout, логируйте причину отказа, возвращайте понятный статус, а не мусорный 200 с поломанным payload. Ретрай-политика решает всё: если трансформация не прошла, лучше упасть предсказуемо, чем разослать битые события по очереди.

Итог простой: трансформация JSON на gateway хороша только там, где она прозрачна, воспроизводима и тестируется как контракт. Всё остальное — в отдельный сервис, иначе ночью будете ловить 5xx на ровном месте.


channel_name: Автоматизация на вебхуках

content:
<b>HMAC-подпись для вебхука: без неё входящий запрос — просто шум в сети</b>

Смотрим в тело запроса: HMAC валидируется только по каноническому payload, а не по «почти такому же» JSON. Если провайдер считает подпись по сырым байтам, не трогайте порядок полей, пробелы и перевод строки до проверки. Любая нормализация до verify ломает подпись и рожает фантомные 401.

Базовый поток такой: берёте raw body, заголовок с подписью, секрет из хранилища, считаете digest и сравниваете в constant-time. В Python это обычно hmac.new(secret, raw, sha256).digest(), затем compare_digest. В Go — hmac.Equal. В Node.js — timingSafeEqual. Никаких `==`, иначе привет, side-channel.

Проверяйте ещё три вещи: корректный алгоритм в заголовке, свежесть timestamp и допустимый skew. Если подпись верна, но timestamp старый — это уже replay, а не «какой-то глюк API». Для нестабильных интеграций кладите event_id в дедуп-таблицу и делайте идемпотентную обработку. Ретрай-политика решает всё.

Логи нужны без секретов и без полного тела, если там PII. Достаточно request_id, полученного хеша, результата verify и причины отказа. Если подпись не сошлась, отвечайте 401 быстро и без лишней болтовни — внешний сервис должен понять, что хук не принят, а не что вы «подумаете потом».