APIの変更

Webhookチェンジログ、誰も求めなかった破壊的変更

1分で読めます

REST APIのチェンジログが存在するのは、呼び出し側が理解できないレスポンスを拒否できる、あるいは少なくとも誰かが気づく程度に大きな声でエラーをログに残せるからだ。Webhookの受信側はそのどちらもめったにしない。POSTを受け取り、期待するフィールドを読み、あるフィールドが移動したり型が変わったり消えたりすると、エンドポイントは誰も監視していないバックグラウンドジョブの中で静かにクラッシュするか、もっと悪いことに、一度も検証しなかった誤った値のまま動き続ける。破壊的変更とは何かは一般的な定義を扱っている。Webhookのペイロードには独自の答えが必要だ。誰かが意図的に呼び出すエンドポイントとは失敗の仕方が違うからだ。

なぜWebhookのペイロード変更はAPIレスポンスの変更と違う壊れ方をするのか

リクエストの向きが逆だからだ。RESTの呼び出し側は呼び出しを開始し、バージョンヘッダーを追加したり、4xxで再試行したり、レスポンス内の非推奨通知を読んだりできる。Webhookの受信側はそのどれも開始していない。あなたのサーバーが送ることを決め、いつ送るかを決め、本文がどんな形になるかを決めた。受信側の唯一の手段は、統合を構築したときに書いた検証だ。そしてほとんどの統合は一度構築されて動作し、壊れるまで誰も見直さない。この非対称性こそが、Webhookのペイロード変更が、呼び出し側が能動的に要求したレスポンス本文の同じ変更よりも慎重さに値する理由のすべてだ。

Webhookのペイロードで実際に破壊的変更とみなされるものは何か

変更ほとんどの受信側にとって破壊的か
新しいフィールドの追加受信側が未知のフィールドを無視するなら、いいえ(この前提を検証せよ、思い込むな)
フィールドの削除何かがそれを読んでいるなら、はい
フィールドの名前変更古いものを削除するのと機能的に同一なので、はい
フィールドの型変更(文字列からオブジェクトへ)ほぼ常に、はい
JSON本文のフィールドの並び替えキーでパースするすべての受信側にとって、いいえ(全員がそうであるべきだ)
イベント名や種類の変更受信側がそれでフィルタやルーティングをしているなら、はい

「フィールドを追加するのは安全だ」という行は、チームが最も頼りにしていて、思い込むのではなく検証する価値が最もある行だ。寛容なJSONパーサーはデフォルトで未知のフィールドを無視するが、厳密なスキーマにデシリアライズする受信側、いくつかの型付き言語は追加設定なしにこれを行う、は予期しないフィールドが現れた瞬間にペイロード全体を拒否しうる。フィールドを追加するのがあなたのWebhookにとって安全なのは、受信側がどうパースするかを知っている場合だけであり、JSON自体が寛容だからではない。

Webhookのペイロードはどうバージョニングすべきか

APIレスポンスの場合とほぼ同じだが、一つ違いがある。受信側はリクエストを送らないので、バージョンを要求できず、送信側がそれを明示しなければならない。それは本文に入れることも、配信そのもののリクエストヘッダーに入れることもできる。GitHubの配信はX-GitHub-EventとX-GitHub-Hook-IDを持ち、Standard Webhooksの仕様はメタデータをwebhook-*ヘッダーに入れる。ペイロード内のバージョンフィールド("payload_version": 2)は最も安価な選択肢で、受信側がそれで分岐する意思があれば機能する。バージョン付きイベントタイプ(invoice.updatedが、受信側が任意で購読する別のイベントとしてinvoice.updated.v2になる)は構築により多くの手間がかかるが、古い形式が一度も移行していない相手に流れ続けることを意味し、これはRESTエンドポイントよりもここでは重要だ。すべての受信側に電話して更新を頼むことはできないからだ。Webhookエンドポイントの登録時に選択される購読ごとの設定は、決定を毎回の配信で分岐させる代わりに前倒しにする。すでに購読レコードがあってそこに付けられる場合は正しい選択だ。

POST /receiver-endpoint
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

そもそも誰が聞いているかをどう知るのか

APIチェンジログにおけるこの問題の同等版よりも悪い。Webhookには呼び出し側を特定するあなた側の受信リクエストログが存在しないからだ。あるのはエンドポイントが200を受け取ったことを示す、自分自身の送信配信ログだけであり、本文で何をしたかはわからない。少なくとも二つのことを追跡せよ。内部APIチェンジログが内部の消費者に推奨するのと同じ規律で、所有者付きの登録済みエンドポイントすべてと、ペイロード変更後のエンドポイントごとの配信失敗率だ。変更直後のエンドポイントからの4xxや5xxレスポンスの急増は、手に入るスタックトレースに最も近いものであり、多くの場合、受信側が壊れたことを示す唯一のシグナルだ。運用しているチームがそれに何日も気づかないことがあるからだ。

Webhookチェンジログは APIチェンジログと分けるべきか

同じページの別セクションであり、別の公開物ではない。APIチェンジログはすでに誰が読み、どう購読するかを確立している。Webhookのペイロード変更は同じフィードに属し、「これは自分の統合に影響するか」を探す受信側の開発者がフィルタできるほど明確にラベル付けされるべきだ。Webhookの消費者は一般的なAPIチェンジログを確認する他の理由をほとんど持たず、誰かが直接そこへ導かない限り見つけられないからだ。

Webhookのペイロードに対する妥当な非推奨期間はどう見えるべきか

同等のREST非推奨よりも長くあるべきだ。受信側での移行は通常、直接のつながりがないかもしれない第二のチームが、独自の緊急性なしにそれに気づき、計画し、リリースする必要があることを意味するからだ。受信側がまだ寛容なライブラリでパースしている可能性が高いフィールドには、一か月が妥当な下限だ。厳密なスキーマなら完全に拒否するフィールド削除には、三か月以上が安全だ。可能な場合は期間中、古い形式と新しい形式を一緒に送れ(古いstatusフィールドとそのバージョン2の置き換えが同じペイロードに入る)。古いフィールドを読む受信側はコードに触れずに動作し続け、すでに移行済みの受信側はもう必要のないフィールドを単に無視するからだ。

FAQ

Webhookの消費者は公開前にペイロード変更を確認する必要があるか? デフォルトでは確認の仕組みは存在しない。まさにそれゆえに非推奨期間はRESTのAPIよりもここで重要になる。誰も準備完了を確認しないため、古い形式が消える前にほとんどの受信側が自分のペースで移行できるだけの長さが期間に必要だ。

未知のフィールドを通知なしに追加しても安全な場合はあるか? 受信側が寛容にパースすることを、思い込むのではなく検証した後だけだ。チェンジログの一項目はほとんどコストがかからず推測を排除する。「JSONパーサーは余分なものを無視する」という思い込みで静かにフィールドを追加すると、厳密なデシリアライズを行うすべての受信側が壊れる。

ペイロード変更後に壊れたWebhookの受信側を検出する最速の方法は何か? 変更直後の数時間で観察される、エンドポイントごとの配信失敗率だ。何が壊れたかは教えてくれず、何かが壊れたことしか教えないが、それが手に入る最も早く、多くの場合唯一のシグナルだ。

再試行ロジックは受信側がペイロード変更を生き延びる助けになるか? ならない。再試行は同じ新しいペイロードを再送するだけで、受信側がパースできる形式には戻らない。ペイロード変更は最初の配信でも、その後のすべての再試行でも同じように受信側を壊す。


この記事の技術的な記述は第三者による確認を受けていません。誤りがあればお知らせください。修正します。

changeloopの関連ページ: 開発者向けドキュメント, changelogツール比較

changeloop
ループを閉じるchangelogを作っているチームです。ユーザーが何かを求め、あなたのチームがそれを届け、求めた人がそれを知る。