$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогДизайн эндпоинтов для вебхуков: подписи, ретраи и защита от повторной доставки

Дизайн эндпоинтов для вебхуков: подписи, ретраи и защита от повторной доставки

$ sudo teach IT
·13 августа 2026 г.·14 мин·36
Дизайн эндпоинтов для вебхуков: подписи, ретраи и защита от повторной доставки

Разберём, как проектировать webhook-контракты так, чтобы интеграции переживали таймауты, дубликаты и перестановки событий. Поговорим про валидацию подписи, идемпотентные ключи, порядок обработки и безопасные коды ошибок для партнёра.

Содержание
1) Базовая модель: что именно ломается1.1 Нет гарантии «ровно один раз»1.2 Порядок событий не гарантирован1.3 Ошибка «в обработке» vs ошибка «в доставке»2) Контракт webhook: минимальный набор полей2.1 Рекомендуемые идентификаторы2.2 Контроль версии формата3) Подписи запросов: как валидировать без дыр3.1 Типичный сценарий: HMAC3.2 Важно: подпись следует считать по каноническому телу3.3 Replay-атаки: где timestamp действительно нужен3.4 Логи: осторожно с секретами4) Идемпотентность: как «пережить» повторную доставку4.1 Идемпотентность по eventid4.2 Что делать с параллельными ретраями: «processing» vs «processed»4.3 Идемпотентность при отсутствии eventid5) Порядок событий: как не сломать бизнес-логику5.1 Версионирование состояния: монотонные поля5.2 “Out-of-order” при распределённой обработке5.3 Нельзя полагаться на «первое пришло — первое обработалось»6) Быстрая и надёжная архитектура endpoint: “accept fast, process later”6.1 Почему 2xx должен приходить быстро6.2 Подход: 2 фазы7) Коды ошибок и договорённость с партнёром: что реально важно7.1 Практическая матрица7.2 Ошибка в обработке vs ошибка в принятии7.3 Ошибки валидации подписи: всегда 4xx7.4 Ошибки бизнес-валидации: чаще 2xx + внутренний статус8) Типовые сценарии и как на них отвечать8.1 Дубль event после таймаута8.2 Перестановка: “cancel” пришёл раньше “paid”8.3 Несовместимая схема payload9) Паттерн “raw body + хэш”: аудит и защита от спорных данных10) Пример целостного решения (контур)11) Как тестировать webhook-контракт: чеклист11.1 Тесты подписи11.2 Идемпотентность11.3 Порядок11.4 Ошибки ретраев12) Что в итоге должно быть в вашем webhook-контрактеВывод

Вебхук — это, по сути, инверсия управления: ваш сервис не «пулит» события, а получает их снаружи. На первый взгляд задача проста: принять HTTP-запрос, распарсить JSON и обработать. Но на практике вебхуки — одна из самых частых точек отказов в интеграциях. Причины банальны: сети теряют пакеты, партнёры ретраят доставку, события приходят не в том порядке, что ожидалось, а ретраи могут приводить к повторной обработке.

Хороший webhook-контракт — это набор правил: как подписывать запросы, как обрабатывать дубликаты, как справляться с перестановками событий, как корректно отвечать коды ошибок, чтобы партнёр понимал, можно ли ретраить. Ниже разберём, как проектировать endpoint так, чтобы интеграция выдерживала таймауты, повторную доставку и «хаотичный» порядок.


1) Базовая модель: что именно ломается

Прежде чем говорить про механики, полезно зафиксировать, какие свойства вам обещает HTTP и какие — нет.

1.1 Нет гарантии «ровно один раз»

HTTP не гарантирует, что запрос будет доставлен один раз. Между клиентом (партнёром) и сервером (вашим webhook-эндпоинтом) могут происходить:

  • таймаут ответа (партнёр не получил 2xx и ретраит);
  • временные сбои (партнёр ретраит с экспоненциальной задержкой);
  • сетевые задержки, из-за которых два запроса приходят в другом порядке.

В результате один и тот же «логический» event может быть доставлен несколько раз. Иногда даже с частично отличающимися payload (например, партнёр мог пере-сформировать данные).

1.2 Порядок событий не гарантирован

Даже если события отправляются «последовательно» по логике партнёра, в реальности при ретраях и параллелизме порядок может нарушаться. Особенно часто это проявляется при:

  • параллельной доставке для разных типов событий;
  • масштабировании партнёра или вашего endpoint;
  • долгой обработке (пока вы не успели сохранить/зафиксировать событие, следующее уже прилетело).

1.3 Ошибка «в обработке» vs ошибка «в доставке»

Партнёр обычно интерпретирует ваши HTTP-коды так:

  • 2xx — «доставлено, можно прекращать ретраи»;
  • 4xx — «невалидно/неразрешимо, ретраить не стоит»;
  • 5xx — «временно не работает, ретраить можно».

Если вы вернёте 5xx при детерминированной проблеме (например, подпись неправильная), вы сами спровоцируете бесконечные ретраи и лавину логов. Если вы вернёте 2xx, но реально не обработали событие из‑за внутренней ошибки — вы потеряете консистентность.


2) Контракт webhook: минимальный набор полей

Чтобы гарантировать устойчивость, webhook должен не просто «принять JSON», а иметь идентификаторы и метаданные, которые позволяют построить идемпотентность.

2.1 Рекомендуемые идентификаторы

В идеале в payload или в заголовках партнёра должны быть:

  • event_id (уникальный идентификатор события);
  • delivery_id (идентификатор попытки доставки — полезен, но не обязателен);
  • created_at / timestamp (время формирования события);
  • event_type (тип события);
  • signature-контекст (см. ниже про подпись);
  • webhook_id / subscription_id (если у партнёра несколько подписок).

Если партнёр не даёт event_id, вам придётся строить идемпотентность по комбинации полей (например, type + order_id + status + updated_at). Это хуже, но часто возможно.

2.2 Контроль версии формата

Добавьте возможность пережить смену схемы: храните schema_version или хотя бы логируйте event_type + структуру payload. Для хранения удобно записывать raw_body (или хэш raw body) — это помогает разбирать инциденты.


3) Подписи запросов: как валидировать без дыр

Подпись — это ваша защита от поддельных запросов и важный элемент для корректного кода ошибок.

3.1 Типичный сценарий: HMAC

Наиболее распространённая модель — HMAC (например, sha256) по телу запроса и ключу из настроек webhook.

Пример заголовков, которые часто встречаются (названия зависят от партнёра):

  • X-Signature — подпись;
  • X-Signature-Timestamp — время (для защиты от replay-атак через старые запросы).

Даже если партнёр использует HMAC без timestamp, вы всё равно должны валидировать подпись перед любыми действиями.

3.2 Важно: подпись следует считать по каноническому телу

Подводный камень: если вы посчитаете подпись по «распарсенному» JSON (например, re-serialize с другим порядком ключей), подпись может не совпасть. Поэтому:

  • берите «сырой» raw_body как получил сервер (bytes);
  • подпись считайте по bytes в том виде, как получен запрос.

Ниже пример (Node.js/Express) с использованием raw body. В production убедитесь, что middleware настроен на сбор raw bytes.

code
import crypto from "crypto";
import express from "express";

const app = express();

// Важно: rawBody должен быть доступен как buffer
app.post("/webhooks", express.raw({ type: "*/*" }), async (req, res) => {
  const rawBody = req.body; // Buffer

  const signature = req.get("X-Signature");
  const ts = req.get("X-Signature-Timestamp"); // если есть
  const secret = process.env.WEBHOOK_SECRET;

  if (!signature) {
    // Если нет подписи — детерминированная проблема контракта
    return res.status(400).json({ error: "Missing signature" });
  }

  // (опционально) проверка допустимого дрейфа времени
  if (ts) {
    const ageMs = Date.now() - Number(ts) * 1000;
    if (ageMs < 0 || ageMs > 5 * 60 * 1000) {
      return res.status(401).json({ error: "Stale signature" });
    }
  }

  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // timing-safe compare
  const ok =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!ok) {
    // Подпись невалидна: не ретраим
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Только после успешной валидации подписи
  const body = JSON.parse(rawBody.toString("utf-8"));

  // TODO: идемпотентная обработка
  res.status(200).json({ received: true });
});

3.3 Replay-атаки: где timestamp действительно нужен

Если у партнёра есть timestamp в сигнатурном контексте — используйте. В противном случае теоретически атакующий может повторить старый payload (хотя на практике это сложно, но защита лишней не бывает).

Рекомендация: задайте допустимое окно (например, 5 минут), учитывая погрешности времени и ретраи.

3.4 Логи: осторожно с секретами

Никогда не логируйте secret и не логируйте подпись целиком в публичных системах. Для отладки можно логировать первые/последние символы хэша.


4) Идемпотентность: как «пережить» повторную доставку

Идемпотентность — это ключ к стабильной интеграции. Идея: при получении одного и того же event повторное выполнение не меняет итог.

4.1 Идемпотентность по event_id

Если партнёр присылает event_id, ваша схема самая простая:

  • перед обработкой попытайтесь «зарезервировать» event_id в БД;
  • если запись уже существует — верните 2xx и прекратите обработку.

Критически важно: резервирование должно быть атомарным (в транзакции), чтобы два параллельных запроса не обработали одно и то же.

Ниже пример с PostgreSQL (псевдо-ORM и чистым SQL по смыслу).

Таблица

code
create table webhook_events (
  event_id text primary key,
  event_type text not null,
  received_at timestamptz not null default now(),
  status text not null default 'processing',
  payload_hash text not null,
  processed_at timestamptz,
  error_code text
);

Логика (концептуально)

code
-- 1) Пытаемся создать запись
insert into webhook_events(event_id, event_type, payload_hash, status)
values ($1, $2, $3, 'processing')
on conflict (event_id) do nothing
returning event_id;

-- 2) Если вернули строку — вы первый обработчик
-- иначе — событие уже было обработано/обрабатывается, возвращайте 2xx

4.2 Что делать с параллельными ретраями: «processing» vs «processed»

Если вы сразу ставите status='processing', а обработка занимает время, возможен сценарий:

  1. первый запрос начал обработку, записал processing;
  2. он таймаутнулся/упал до изменения статуса;
  3. партнёр ретраит тот же event;
  4. второй запрос увидел processing и решил, что всё ок — и не обработал повторно.

Чтобы не попасть в «вечный processing», используйте один из подходов:

  • либо храните processed_at и возвращайте 2xx только если есть processed_at;
  • либо храните версию состояния и делайте восстановление по таймеру (например, если processing старше N минут — можно повторить).

Практичный вариант: после успешной обработки обновлять строку на processed. Повторный запрос:

  • если processed_at заполнено — идемпотентный дубль → 2xx;
  • если processed_at пусто → смотрим, не «зависло» ли processing по времени, и решаем: либо повторяем, либо ставим в очередь.

4.3 Идемпотентность при отсутствии event_id

Если партнёр не даёт уникальный идентификатор события, сделайте хэш от «канонического ключа»:

  • type + subject_id + status + timestamp (или updated_at);
  • или хэш от отсортированного JSON subset (но осторожно: сложно сделать канонизацию без ошибок).

Вы всё равно должны помнить, что такая схема не гарантирует 100%: если партнёр изменит поле или формат timestamps, хэш станет другим.


5) Порядок событий: как не сломать бизнес-логику

Идемпотентность отвечает за «один и тот же event». Но порядок — это «какой event важнее», если пришло несколько.

5.1 Версионирование состояния: монотонные поля

Частый паттерн: для сущности (например, заказ) храните монотонный version или updated_at. Тогда:

  • при получении события сравните event_timestamp/sequence с текущим состоянием;
  • если событие «старее» — не применяйте изменения, но верните 2xx.

Например, заказ уже в статусе paid, а пришёл created с более ранним timestamp — игнорируем.

5.2 “Out-of-order” при распределённой обработке

Если обработка асинхронная (вы принимаете HTTP быстро и отправляете в очередь), события могут прийти в очередь по порядку, но обработаться не так, как доставлены. Поэтому важно:

  • либо обрабатывать события последовательно для одного subject_id (например, partition key в очереди);
  • либо внутри обработки проверять версию/временную метку.

Второй вариант чаще проще, потому что он не зависит от очереди.

5.3 Нельзя полагаться на «первое пришло — первое обработалось»

Это одна из самых типичных ошибок. Даже если партнёр обещает порядок, ретраи и повторная доставка разрушат гарантию. Поэтому порядок нужно явно моделировать данными (sequence/version/timestamp) или бизнес-правилами.


6) Быстрая и надёжная архитектура endpoint: “accept fast, process later”

Цель webhook endpoint — отвечать быстро, после проверки подписи и записи идемпотентного следа. Долгие операции оставляйте на асинхронную обработку.

6.1 Почему 2xx должен приходить быстро

Если вы делаете синхронно:

  • вызовы к другим сервисам,
  • расчёты,
  • запись больших объёмов,
  • доступ к внешним API,

то вы повышаете шанс таймаута. Таймаут превращается в ретраи → дубль → рост нагрузки.

6.2 Подход: 2 фазы

  1. Синхронная фаза: валидация подписи, идемпотентная запись, enqueue job.
  2. Асинхронная фаза: обработка, обновление статуса, запись эффектов (изменение доменной модели).

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

code
def webhook_handler(request):
    raw = request.raw_body
    verify_signature(raw, headers)

    event = json.loads(raw)
    event_id = event["event_id"]

    created = idempotency_reserve(event_id, event)
    if not created:
        # уже обработано или обрабатывается — 2xx
        return 200

    enqueue_processing_job(event_id)

    # фиксируем, что событие принято в обработку
    return 200

7) Коды ошибок и договорённость с партнёром: что реально важно

Самая недооценённая часть webhook-дизайна — корректная сигнализация партнёру, можно ли ретраить.

7.1 Практическая матрица

Ориентир (может отличаться у конкретного провайдера, но логика универсальна):

  • 200–299: событие принято и обработка/фиксация выполнена. Ретраить не нужно.
  • 400: запрос не соответствует формату контракта (например, отсутствуют обязательные поля). Ретраить бессмысленно.
  • 401/403: подпись неверная или нет прав. Ретраить тоже бессмысленно.
  • 409: конфликт идемпотентности/версии (в некоторых схемах). Но осторожно: многие провайдеры трактуют 409 как ретраить или нет — уточняйте. Часто лучше возвращать 2xx при дубле и решать в БД.
  • 429: вы ограничили скорость. Некоторые партнёры ретраят.
  • 500–503: временная ошибка сервера. Ретраить можно.

7.2 Ошибка в обработке vs ошибка в принятии

Если вы приняли событие и отдали 2xx, но затем в обработке упали и вы не смогли завершить эффект — партнер не узнает и не ретраит. Поэтому вам нужно:

  • либо делать enqueue в той же логике идемпотентности (чтобы не потерять job);
  • либо держать обработку так, чтобы «успех HTTP» означал «событие зафиксировано как принятый к обработке».

Если у вас есть очереди, важно уметь ретраить job внутри себя.

7.3 Ошибки валидации подписи: всегда 4xx

Подпись — детерминированная проверка. Если она не совпала, повторная доставка того же payload не поможет. Поэтому корректнее отвечать 401/403, чтобы партнёр не зацикливался на ретраях.

7.4 Ошибки бизнес-валидации: чаще 2xx + внутренний статус

Допустим, событие содержит корректную подпись, но «не соответствует текущему состоянию» (например, заказ в отменённом статусе, а прилетело событие оплаты). В зависимости от контрактов вы можете:

  • игнорировать событие (но вы должны записать, что это было);
  • или вернуть 4xx (но это может спровоцировать бесконечные ретраи, если партнер будет продолжать отправлять корректно подписанные payload).

Чаще в интеграциях лучше возвращать 2xx и корректно фиксировать outcome у себя, потому что ретраи не исправят логическую несовместимость.


8) Типовые сценарии и как на них отвечать

Разберём несколько частых кейсов.

8.1 Дубль event после таймаута

Схема:

  1. endpoint успешно зарезервировал event_id;
  2. затем операция ушла в асинхронную обработку;
  3. но HTTP ответ не дошёл (или ваш сервер упал после резерва, но до 2xx);
  4. партнёр ретраит.

Правильное поведение:

  • на ретрае вы увидите event_id и состояние (processing/processed);
  • если уже обработано — вернуть 2xx;
  • если processing завис — либо повторить, либо продолжить (в зависимости от механизма).

8.2 Перестановка: “cancel” пришёл раньше “paid”

Если у вас есть версия/временная метка, вы можете:

  • применить только событие с наиболее свежей версией;
  • «старые» события игнорировать.

Если бизнес требует строгого порядка (например, нельзя отменить до оплаты), используйте state machine и храните последовательность. В любом случае важно не «обрабатывать в порядке доставки».

8.3 Несовместимая схема payload

Если вы меняете контракт, а партнёр присылает другой формат, подпись может быть верной, но JSON структура не соответствует.

Лучшее:

  • валидация схемы (JSON Schema / Pydantic / Zod и т.п.);
  • при детерминированной несовместимости — 400, чтобы партнёр не ретраил бесконечно.

9) Паттерн “raw body + хэш”: аудит и защита от спорных данных

Даже при правильной идемпотентности иногда возникают разногласия: «мы отправили event A, но у вас в системе другое». Чтобы разрулить инциденты:

  • храните payload_hash от raw_body;
  • храните event_id, event_type, received_at;
  • при повторном event_id сравнивайте текущий hash с сохранённым (это помогает обнаружить случаи, когда партнёр переотправляет с изменённым содержимым).

Если изменилось содержимое при том же event_id — это ошибка партнёра или нарушение контракта. Вы должны зафиксировать это и принять решение по правилам: либо обновить данные, либо оставить как было и пометить инцидент.


10) Пример целостного решения (контур)

Ниже пример на Python (FastAPI + PostgreSQL-идея). Он не привязан к конкретной очереди, но показывает ключевые шаги: подпись → идемпотентная запись → быстрое 2xx → асинхронная обработка.

code
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib
import json
from datetime import datetime, timezone

app = FastAPI()

SECRET = b"your_webhook_secret"

def verify_signature(raw_body: bytes, signature_hex: str) -> bool:
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    a = bytes.fromhex(signature_hex)
    b = bytes.fromhex(expected)
    # timing-safe compare
    return len(a) == len(b) and hmac.compare_digest(a, b)

def idempotency_reserve(conn, event_id: str, event_type: str, payload_hash: str):
    # Должно быть атомарно
    # Возвращает True, если мы "первые", иначе False
    with conn.cursor() as cur:
        cur.execute("""
            insert into webhook_events(event_id, event_type, payload_hash, status)
            values (%s, %s, %s, 'processing')
            on conflict (event_id) do nothing
            returning event_id;
        """, (event_id, event_type, payload_hash))
        row = cur.fetchone()
        return row is not None

def payload_hash(raw_body: bytes) -> str:
    return hashlib.sha256(raw_body).hexdigest()

@app.post("/webhooks")
async def webhook(request: Request):
    raw_body = await request.body()
    sig = request.headers.get("X-Signature")
    if not sig:
        raise HTTPException(status_code=400, detail="Missing signature")

    if not verify_signature(raw_body, sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    body = json.loads(raw_body.decode("utf-8"))
    event_id = body["event_id"]
    event_type = body["event_type"]

    ph = payload_hash(raw_body)

    # conn = get_db_connection()
    # created = idempotency_reserve(conn, event_id, event_type, ph)
    created = True  # заглушка для примера

    if not created:
        # Дубликат: корректно возвращаем 2xx
        return {"received": True, "duplicate": True}

    # enqueue(event_id)  # асинхронная обработка
    # mark accepted -> обработчик потом обновит status/processed_at
    return {"received": True, "duplicate": False}

Ключевая мысль: HTTP обработчик не должен решать бизнес-конфликты «по месту», если это затягивает ответ. Он должен гарантировать: событие не потеряно и не обработано дважды в критической секции.


11) Как тестировать webhook-контракт: чеклист

Устойчивость не появляется «по умолчанию». Стоит заранее протестировать:

11.1 Тесты подписи

  • неправильный секрет → 401;
  • изменение одного байта body → 401;
  • отсутствие подписи → 400;
  • проверка replay (если есть timestamp).

11.2 Идемпотентность

  • отправьте один event_id дважды параллельно;
  • имитируйте сценарий, где первая попытка «зависла» после резервирования;
  • проверьте, что итоговый эффект не дублируется.

11.3 Порядок

  • отправьте sequence A затем B, но обработайте их в обратном порядке;
  • убедитесь, что применяется правильная версия состояния.

11.4 Ошибки ретраев

  • симулируйте временную ошибку очереди/БД в асинхронной части и убедитесь, что ретраи происходят там, где нужно;
  • проверьте, что на ретраи партнёр не получает 4xx/5xx «не по делу».

12) Что в итоге должно быть в вашем webhook-контракте

Если собрать всё в краткий список требований к endpoint:

  1. Валидация подписи по raw_body до любых действий.
  2. Идемпотентность по event_id (или устойчивому ключу), атомарная запись в БД.
  3. Обработка дублей: при подтверждённой фиксации — всегда 2xx.
  4. Учет out-of-order через версию/временную метку/sequence в доменной модели.
  5. Быстрый HTTP: accept → enqueue → обработка отдельно.
  6. Корректные коды ошибок: 4xx для детерминированных ошибок контракта, 5xx только для временных.
  7. Аудит: payload_hash и хранение метаданных для расследований.
  8. План на очереди/ретраи внутри системы: не потерять job после 2xx.

Вывод

Webhook-эндпоинт — это не «ручка для POST», а часть распределённой системы с реальными ограничениями: нет гарантий доставки ровно один раз и нет гарантии порядка событий. Чтобы интеграция переживала таймауты, ретраи и перестановки, проектируйте контракт вокруг идемпотентности (event_id), версионирования состояния и безопасной валидации подписи. Отдельно продумайте коды ошибок: ваш HTTP ответ — это сигнал партнёру, ретраить или прекращать.

Если вы хотите разобрать это глубже на практике (с разбором кейсов, схемами идемпотентности и примерами тестирования интеграций), полезно пройти системный материал по теме, например курс по вебхукам и дизайну API [на /course/] — но в любом случае главная работа начинается с правильного контракта и дисциплины обработки событий на вашей стороне.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

Точки отказа в интеграциях: таймауты, ретраи и идемпотентность как единая система
точки

Точки отказа в интеграциях: таймауты, ретраи и идемпотентность как единая система

Покажем, как спроектировать поведение при сбоях сети и внешних сервисов: где ретраить, где падать, как считать дедлайны и как не дублировать операции. Без теории ради теории — только рабочие схемы.

26 июля 2026 г.
510
Интеграции с платежами криптовалют: идемпотентность, подписи и проверка вебхуков
интеграции

Интеграции с платежами криптовалют: идемпотентность, подписи и проверка вебхуков

На примере aiosend обсудим, как строить надёжную обработку событий оплаты: статусы, повторные уведомления и валидация входящих данных.

23 июля 2026 г.
440
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1151
Что такое переменная простыми словами: примеры из жизни и первый код
такое

Что такое переменная простыми словами: примеры из жизни и первый код

Разберём, что такое переменная без терминов: как “хранить” значение в памяти и как читать/менять его в программе. Дальше — мини-примеры на вводе/выводе и задания для новичка, чтобы закрепить понимание прямо в коде.

25 сентября 2026 г.
50
FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
800
Какой первый проект выбрать новичку, чтобы не бросить обучение
первый

Какой первый проект выбрать новичку, чтобы не бросить обучение

Подберём 5–7 идей под уровень “с нуля”, объясним, что делать по шагам и как довести проект до результата без перегруза. В конце — как оформить мини-портфолио и что показать, даже если проект маленький.

23 сентября 2026 г.
250

Комментарии

Пока нет комментариев

Содержание

1) Базовая модель: что именно ломается1.1 Нет гарантии «ровно один раз»1.2 Порядок событий не гарантирован1.3 Ошибка «в обработке» vs ошибка «в доставке»2) Контракт webhook: минимальный набор полей2.1 Рекомендуемые идентификаторы2.2 Контроль версии формата3) Подписи запросов: как валидировать без дыр3.1 Типичный сценарий: HMAC3.2 Важно: подпись следует считать по каноническому телу3.3 Replay-атаки: где timestamp действительно нужен3.4 Логи: осторожно с секретами4) Идемпотентность: как «пережить» повторную доставку4.1 Идемпотентность по eventid4.2 Что делать с параллельными ретраями: «processing» vs «processed»4.3 Идемпотентность при отсутствии eventid5) Порядок событий: как не сломать бизнес-логику5.1 Версионирование состояния: монотонные поля5.2 “Out-of-order” при распределённой обработке5.3 Нельзя полагаться на «первое пришло — первое обработалось»6) Быстрая и надёжная архитектура endpoint: “accept fast, process later”6.1 Почему 2xx должен приходить быстро6.2 Подход: 2 фазы7) Коды ошибок и договорённость с партнёром: что реально важно7.1 Практическая матрица7.2 Ошибка в обработке vs ошибка в принятии7.3 Ошибки валидации подписи: всегда 4xx7.4 Ошибки бизнес-валидации: чаще 2xx + внутренний статус8) Типовые сценарии и как на них отвечать8.1 Дубль event после таймаута8.2 Перестановка: “cancel” пришёл раньше “paid”8.3 Несовместимая схема payload9) Паттерн “raw body + хэш”: аудит и защита от спорных данных10) Пример целостного решения (контур)11) Как тестировать webhook-контракт: чеклист11.1 Тесты подписи11.2 Идемпотентность11.3 Порядок11.4 Ошибки ретраев12) Что в итоге должно быть в вашем webhook-контрактеВывод