Документация LMS версия 2026.09.180

Документация/API/Идемпотентность

Раздел описывает первый рабочий срез API. Срез в разработке: маршрутов ещё нет, экран ключей появится вместе с ним — до выкатки проверить запросы не обо что.

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

Идемпотентность нужна с обеих сторон, и это две разные задачи. На отправке — чтобы повтор запроса не создал второй заказ. На приёме — чтобы повтор вебхука не выдал доступ дважды.

Обе описаны здесь, потому что забывают обычно вторую.

Зачем это на отправке #

Сеть теряет ответы, а не запросы. Клиент отправил создание платежа, получил таймаут — и не знает, создался платёж или нет. Оба варианта плохи: повторить — риск списать дважды, не повторить — риск не списать вовсе.

Ключ идемпотентности снимает выбор: повторять можно всегда.

Как пользоваться #

curl -X POST 'https://school.example.com/api/v1/orders' \
  -H 'Authorization: Bearer lms_live_a8f3c1e9d2b74a5f8e0c1d3b5a7f9e2c' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 6f1e2a34-9c8b-4d7e-a1f0-3b5c7d9e1f2a' \
  -H 'Accept: application/json' \
  --data '{ "user_id": "usr_01HQZX3M8K4N2P", "offer_id": "off_01HQZX7QGVPR6A" }'

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

Повтор с тем же ключом возвращает тот же результат, что и первый запрос, и добавляет заголовок:

Idempotency-Replayed: true

Не создаёт второй заказ, не списывает деньги дважды. Ключ хранится 24 часа.

Откуда брать ключ #

Хороший ключ — тот, который вы можете воспроизвести после перезапуска своего процесса. Случайный uuid, сгенерированный в момент отправки и не сохранённый, защищает только от повтора внутри одной попытки — то есть от самого редкого случая.

Практически: генерируйте ключ вместе с задачей и храните его рядом с ней. Если задача перезапустилась, она берёт тот же ключ.

Тот же ключ с другим телом #

Это ошибка клиента, и отвечаем мы 409 с пояснением.

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

Где ключ обязателен #

Там, где повтор опасен: создание заказа, платёж, возврат, выдача доступа, отправка сообщения.

Оговорка честности: в спецификации это сказано прозой, а перечня операций нет. Поэтому в машинной спецификации Idempotency-Key помечен необязательным везде — пометить его обязательным наугад значило бы соврать про контракт. Признак придёт из кода вместе с эндпоинтом.

Практический совет до этого момента: посылайте ключ на всех операциях записи. Лишним он не будет никогда.

Идемпотентность на приёме #

Вторая половина, и её забывают чаще.

Вебхук может прийти дважды: при повторе после таймаута, при переигрывании пропущенного, при обрыве после того, как вы уже всё обработали. Поэтому обработчик обязан быть идемпотентным по event.id:

1. Взять event.id из конверта.
2. Вставить его в свою таблицу обработанных — с УНИКАЛЬНЫМ индексом.
3. Вставка не прошла: событие уже обработано. Ответить 200 и выйти.
4. Вставка прошла: обработать.

Именно уникальный индекс, а не проверка «есть ли такая запись» перед вставкой: два экземпляра обработчика пройдут проверку одновременно и оба обработают. Гонка редкая — и потому находится в самый неудобный момент.

То же самое делаем и мы у себя на приёме уведомлений от платёжных провайдеров: уникальный индекс по паре «провайдер, внешний идентификатор». Повтор уведомления не создаёт второй платёж и не выдаёт доступ дважды.

Чего идемпотентность не делает #

Не делает операцию обратимой. Возврат платежа идемпотентен по ключу, но необратим по сути: повтор с тем же ключом вернёт тот же результат, а новый ключ сделает второй возврат.

Не заменяет проверку состояния. Отмена уже отменённого заказа даст 409 — это конфликт состояния, а не задача для ключа.

Эта страница в Markdown