Изменения API

Changelog вебхуков: breaking change, который никто не просил

5 мин чтения

Changelog REST API существует потому, что вызывающий может отклонить ответ, который не понимает, или хотя бы залогировать ошибку достаточно громко, чтобы кто-то заметил. Получатель вебхука редко делает то или другое. Он получает POST, читает ожидаемые поля, и если поле переместилось, сменило тип или исчезло, эндпоинт либо тихо падает внутри фонового job’а, за которым никто не следит, либо, хуже, продолжает работать с неверным значением, которое никогда не валидировал. Что такое breaking change разбирает общее определение; payload вебхука нуждается в собственном ответе, потому что режим отказа отличается от эндпоинта, который кто-то вызывает намеренно.

Почему изменение payload вебхука ломается иначе, чем изменение ответа API?

Потому что направление запроса перевёрнуто. Вызывающий REST инициирует вызов и может добавить заголовок версии, повторить попытку при 4xx или прочитать уведомление об устаревании в ответе. Получатель вебхука ничего из этого не инициировал: ваш сервер решил отправить, решил когда, и решил, какую форму будет иметь тело. Единственный рычаг получателя — валидация, которую он написал, когда строилась интеграция, а большинство интеграций строятся один раз, работают, и никто их не пересматривает, пока они не сломаются. Эта асимметрия — вся причина, почему изменение payload вебхука заслуживает больше осторожности, чем то же изменение в теле ответа, которое вызывающий активно запросил.

Что на самом деле считается breaking change в payload вебхука?

ИзменениеBreaking для большинства получателей
Добавление нового поляНет, если получатели игнорируют неизвестные поля (проверьте это предположение, не принимайте его на веру)
Удаление поляДа, если что-то его читает
Переименование поляДа, функционально идентично удалению старого
Изменение типа поля (строка в объект)Да, почти всегда
Изменение порядка полей в теле JSONНет, для любого получателя, парсящего по ключу, а такими должны быть все
Изменение имени или типа событияДа, если получатели фильтруют или маршрутизируют по нему

Строка «добавление поля безопасно» — та, на которую команды опираются больше всего, и та, которую больше всего стоит проверить, а не предполагать. Разрешающий JSON-парсер по умолчанию игнорирует неизвестные поля, но получатель, десериализующий в строгую схему, несколько типизированных языков делают это без дополнительной настройки, может отклонить весь payload, как только появится неожиданное поле. Добавление поля безопасно для вашего вебхука только если вы знаете, как парсят получатели, а не потому что сам JSON разрешающий.

Как версионировать payload вебхука?

Почти как для ответа API, с одним нюансом: получатель никогда не отправляет запрос, поэтому не может попросить версию, и её должен указать отправитель. Её можно передать в теле или в заголовке запроса самой доставки; доставки GitHub несут X-GitHub-Event и X-GitHub-Hook-ID, а спецификация Standard Webhooks кладёт свои метаданные в заголовки webhook-*. Поле версии в payload ("payload_version": 2) — самый дешёвый вариант, и он работает, когда получатели готовы ветвиться по нему. Версионированный тип события (invoice.updated становится invoice.updated.v2 как отдельное событие, на которое получатель подписывается добровольно) требует больше работы для построения, но означает, что старая форма продолжает поступать тем, кто никогда не мигрировал, что здесь важнее, чем для REST-эндпоинта, потому что вы не можете позвонить каждому получателю с просьбой обновиться. Настройка на подписку, выбранная при регистрации эндпоинта вебхука, принимает решение заранее вместо ветвления при каждой доставке, и это правильный выбор, когда у вас уже есть запись подписки, к которой можно её прикрепить.

POST /endpoint-получателя
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

Как вообще узнать, кто слушает?

Хуже, чем эквивалентная версия этой проблемы в changelog API, потому что у вебхука нет журнала входящих запросов на вашей стороне, который называл бы вызывающего; у вас есть только собственный журнал исходящих доставок, который говорит, что эндпоинт получил 200, а не что он сделал с телом. Отслеживайте минимум две вещи: каждый зарегистрированный эндпоинт с владелицей, ту же дисциплину, что changelog внутренних API рекомендует для внутренних потребителей, и вашу долю неудачных доставок на эндпоинт после изменения payload. Всплеск ответов 4xx или 5xx от эндпоинта сразу после изменения — ближайшее к трассировке стека, что вы получите, и часто единственный сигнал, что получатель сломался, потому что команда, которая им управляет, может не заметить это днями.

Должен ли changelog вебхуков быть отдельным от changelog API?

Отдельный раздел на той же странице, а не отдельная публикация. Changelog API уже устанавливает, кто его читает и как на него подписываются; изменение payload вебхука принадлежит той же ленте, помеченное достаточно ясно, чтобы разработчица на стороне получателя, сканирующая «затрагивает ли это мою интеграцию», могла отфильтровать по нему, потому что у потребителя вебхука часто нет другой причины проверять общий changelog API, и он найдёт его, только если кто-то направит его туда напрямую.

Как должно выглядеть разумное окно устаревания для payload вебхука?

Длиннее эквивалентного устаревания REST, потому что миграция на стороне получателя обычно означает, что вторая команда, с которой у вас может не быть прямой связи, должна заметить это, запланировать и выпустить без собственной срочности. Месяц — разумный минимум для поля, которое получатель, вероятно, всё ещё парсит разрешающей библиотекой; три месяца или больше безопаснее для удаления поля, которое строгая схема полностью отклонит. Отправляйте старую и новую форму вместе в течение окна, когда это возможно (старое поле status и его замена из версии 2 в одном payload), потому что получатель, читающий старое поле, продолжает работать, не трогая свой код, а тот, кто уже мигрировал, просто игнорирует поле, которое ему больше не нужно.

FAQ

Должны ли потребители вебхуков подтверждать изменение payload перед публикацией? По умолчанию не существует механизма подтверждения, и именно поэтому окно устаревания важнее здесь, чем для REST API: никто не подтверждает готовность, поэтому окно должно быть достаточно длинным, чтобы большинство получателей мигрировали в своём темпе до исчезновения старой формы.

Безопасно ли когда-либо добавлять неизвестные поля без уведомления? Только после того, как вы проверили, а не предположили, что ваши получатели парсят разрешающе. Запись в changelog стоит недорого и убирает неопределённость; тихое добавление полей с предположением, что «JSON-парсеры игнорируют лишнее», ломает любого получателя со строгой десериализацией.

Какой самый быстрый способ обнаружить сломанного получателя вебхука после изменения payload? Доля неудачных доставок на эндпоинт, наблюдаемая в часы сразу после изменения. Она не скажет вам, что сломалось, только что что-то сломалось, но это самый ранний и часто единственный сигнал, который вы получите.

Помогает ли логика повторных попыток получателям пережить изменение payload? Нет. Повторная попытка отправляет тот же новый payload заново; она не возвращается к форме, которую получатель может распарсить. Изменение payload ломает получателя при первой доставке и при каждой последующей повторной попытке одинаково.


Технические утверждения в этой статье не проходили независимую проверку. Если здесь что-то не так, сообщите нам, и мы исправим.

По теме на changeloop: Документация для разработчиков, Сравнение инструментов changelog

changeloop
Команда, которая делает changelog, замыкающий цикл. Пользователи о чём-то просят, ваша команда это делает, тот, кто просил, узнаёт об этом.