世界を動かす技術を、日本語で。

ウェブフックの谷

2026年8月6日原文(weli.dev)

概要

  • 3度にわたり異なる会社・プロバイダーで同じ「データ同期システム」を構築
  • Webhookによる通知ベースのデータ同期の限界と運用負荷を実体験
  • 通知(Webhook)はデータ転送には本質的に不向き
  • 「順序性・完全性・検証性」を満たす設計への進化の必要性
  • ログの直接的な取得(Pull型)へのパラダイムシフト提案

3度目のシステム構築体験

  • 3社で同じ「顧客データ同期システム」 を構築した経験

  • 顧客情報は外部プロバイダー(Identity Provider、Stripe等) のDBに分散管理

  • Webhook購読でローカルにコピー を保持する運用

  • 初回は「エンドポイント1つで済む」と考えたが、実際は複雑化

    • 署名検証 の実装(セキュリティ対策)
    • 重複配信対策(dedupテーブル) の導入
    • 順序ズレ対応のバッファ 設計
    • 初期データ取得(Bootstrap Importer) とライブイベントの競合対策
    • 定期的な整合性チェック(Reconciliation Cron) の実装
  • Cronジョブは「自分のコピーを信用できない」証拠 として毎晩再構築

Webhook運用の本質的な課題

  • データの不整合やロストはサイレントに発生 し、サポート問い合わせで発覚
  • 各プロバイダーごとに管理画面・Webhook設定・署名方式が異なる 運用負荷
  • デバッグ時は複数タブで各種ログを横断的に確認 する必要

通知はデータではない

  • Webhookは「何かが起こった」通知 であり、 データ転送には不向き
  • 順序保証・完全性・検証性・初期同期 など、Webhookが持たない重要要件
  • 本来の履歴(Ordered Log)はプロバイダー内に存在 するが、 分割・順不同・欠損・重複 で通知される
  • 「パズルのピースを郵送で受け取る」ような再構築作業 の比喩

Webhook利用の歴史と現状

  • Webhookは2007年の誕生当初は副作用トリガー用途 (CIビルドや領収書メール等)
  • 「データ同期」用途への拡張で不整合が顕在化
  • 業界全体が「安易な選択肢」に15年かけて複雑なワークアラウンドを積み上げた現状

業界の最適化の罠(ローカルオプティマム)

  • Webhook周辺のエコシステム (Svix、Hookdeck、AWS EventBridge等)の発展
  • 本質的な課題解決ではなく「谷底の快適化」 に留まっている現状
  • ログAPIを提供するStripeやWorkOSのような進化例 も登場

理想的な設計への提案

  • 必要な要件:順序性、初期同期、削除検知、再開性、検証可能性

  • WebhookやリストAPIの強化では限界

  • プロバイダーが「変更ログ(Ordered Change Log)」を直接提供し、 クライアントがPull型で取得する設計 への転換

    • GET /feed/customers?cursor=xxxx のようなAPI
    • 全イベントが順序付き・カーソル管理 で取得可能
    • 重複排除・順序バッファ・初期インポート不要
    • 削除も「削除イベント」として明示
    • 接続はクライアント発、署名・トンネル等も不要
    • 検証用のハッシュや件数もAPIで取得可能

まとめ

  • Webhook通知によるデータ同期は設計的に限界
  • 「Ordered Logの直接取得」こそが本質的な解決策
  • 進化したAPI設計と共通契約の業界標準化が今後の課題
  • 一時的な快適さよりも、根本的な設計変更の必要性

Hackerたちの意見

ウェブフックは厄介な問題だね。具体的に言うと、StripeのイベントAPIは確実にカーソルを送信していて、大規模なユーザーは長い間そのポーリング方式を好んで使ってる。

StripeのイベントAPIは、物事を正しく行う方法の一例だよ。SCROLLは、みんながStripeのようなイベントポーリングAPIを提供できるように共通の仕様を作ろうとしてるだけなんだ。

ウェブフックを使うと、消費者はプロバイダーからの更新に非同期で応答できるんだよね。データが変わってなければ、プロバイダーは更新を送信しない。SCROLLでは、消費者がプロバイダーに更新を求めるタイミングを選ぶ責任がある。データが変わったかどうかを知る仕組みがないと、消費者は悲観的になって、一定の間隔でプロバイダーに新しいデータをポーリングしなきゃいけなくなる。提案には2つの問題があると思う:(1) SCROLLは消費者とプロバイダーの両方にとって不必要なネットワークトラフィックを増やすこと、(2) 消費者がデータが変わったかどうかを知ることができないから、ウェブフックを使った場合よりも消費者のローカルモデルとプロバイダーのデータモデルの間にラグが生じること。

提案されたストリーミングオプションを使ってないなら、その事実だけでウェブフックを送ることもできるんじゃない?つまり、「何かが多分変わったから、まだポーリングしてないならSCROLLを確認した方がいいよ」っていう空の通知を送る感じ。

あなたのAPIが単にKafkaをラップしているだけなら、ロングポーリングができるよ。

ウェブフックはシンプルでどこにでもあるけど、それが弱点でもあり強みでもあるんだよね。だから、たくさんのことに使われてるけど、実際にはあまり得意じゃないこと(状態同期)にも使われてる。こういう弱点があるから、私たち[1]はFIFOエンドポイントやポーリングエンドポイント、そして「Svix Stream」と呼んでいるものを追加して、順序付きの状態同期を実現してる(それぞれにトレードオフがあるけど)。これで人々は自分のユースケースに最適な方法でイベントを消費できるようになるんだ。状態同期をさらに簡単にするために、もっと色々なことに取り組んでるよ。ウェブフックに関して人々が直面している課題についてもっと聞きたいな。私たちはこれらを改善したいから。OP: あなたの考えについてもっと聞かせてほしいし、すぐにメールを送るね。P.S. もし知らなかったら、Standard Webhooks[2]をチェックしてみて。これは署名検証を助けるために作った仕様で、OpenAIやAnthropic、Googleなどに採用されてるよ。一度に一つずつウェブフックの課題に取り組んでるんだ。 :)

最近、Quickbooks APIで全く同じことがあったよ。レスポンスやウェブフックは全く信頼できない。例えばユーザーや請求書を作成するとき、時々エラーが返ってくるけど、実際にはエンティティが作成されてることがある。だから、すべてを作成した後に手動で確認しないと、ちゃんと作成されたかどうかわからないんだ。それに、Quickbooksは更新に時間がかかることがあって、その間に会社ファイルをロックしちゃう。だから、すぐに存在チェックができないし、時々チェックがエラーになったりタイムアウトしたりするから、結局はデータベースを正しく照合できるまで永遠にチェックし続ける必要がある。何百、何千ものトランザクションが毎分発生してるのに、この状態には決して到達できない。常に追いつこうとしてるけど、結局できない状態にいる。Quickbooksの開発サポートにこのことを言ったとき、彼らの返事は「私たちのシステムでちゃんと作成されていることを確認するのはあなたの仕事です」だった。どうしてこんな信頼できないシステムを我慢するようになったんだろう?

それがエンタープライズソフトウェアがシステムとして機能する方法なんだ。すべての中心的な焦点であることを要求する。労働者は、テキストファイルへのアクセスを制御し、追加の有効性をチェックするだけの数行のスクリプトで置き換えられるような、こういう生産性の劇場の無駄を回避したいと思ってる。でもそれは許されないから、実質的に文書化が不十分なOS全体が必要になって、ブローカーの経済を支えることになる。そうしないと、全体の詐欺が崩壊しちゃうんだ。

返答やWebhookを全く信頼できない。まあ…そうだよね。明らかじゃない?ソフトウェアがどんなに気を使っても、うまくいかないことはいくつもあるよ。例えば: - バックエンドのクラスタでトランザクションが完了したけど、アプリのインスタンスが応答を作成する前に死んじゃった。 - トランザクションが完了して、アプリのインスタンスが応答を送信したけど、その間のロードバランサー/リバースプロキシが応答を中継する前に死んじゃった。 - すべてがうまくいったけど、ISPがパケットを落としちゃった。 - すべてが完了してISPも正常だったけど、こっちの応答が悪意のあるものとしてフラグ付けされたり、ロードバランサーを通過できなかったり。だから、一般的にAPIリクエストをしてエラーが返ってきたら、状態が変わったかどうかを確認しなきゃいけない。そうしないと、間違ってるし、エラーを返したシステムを責めることはできないよ。

簡単な分散システムに手を出して、自分がモジュラーアプリケーションの正しい書き方を知ってると思ってる人たちがいるよね。でも、彼らは分散システムに深く入り込むけど、そこについて何も学んでないし、起こりうる問題も理解してない。多くのコンピュータサイエンスの学位プログラムが、ネットワーキングのアプローチやアーキテクチャ設計、すべてを堅牢にするためのアルゴリズムを含む分散システムの内容で2学期もあるのには理由があるんだよ。

Webhookを使った状態同期の問題についての良いまとめだね。提案されている解決策が、SCROLLという擬似IETFスタイルのドラフトプロトコルで、実際のIETFドラフト「Braid-HTTP Subscriptions」に驚くほど似ていることに気づいたよ。両方のドラフトは、GETリクエストとヘッダーでサブスクリプションを要求してるんだ。 Scroll Request: GET /scroll/feed/customers Prefer: stream Braid Request: GET /customers Subscribe: この2つのシステムでは、GETのレスポンスがイベントをストリームするためにオープンになってる。SCROLLはapplication/x-ndjsonで応答するし、Braidのサブスクリプションは209 Multiresponseで、content-typeはapplication/http-history。これにより、JSONだけでなく、CSVやPNG、XML、HTML、プレーンテキスト、その他のメディアタイプもサポートできるんだ。著者は、採用が難しいって言ってたけど、Webhookが一般的なのは、標準的なHTTPだからなんだよね。これが採用されるためには、標準的なHTTPに組み込む必要がある。だから、IETFに行って、状態同期をサポートするためにHTTPを一般的に拡張する必要があるんだ。既存のHTTPメディアタイプ(JSONだけじゃなく)や、リソース/URL(特別な/scroll/* URLだけじゃなく)、タイムスタンプのマーク方法(SCROLLで提案されている順序付き文字列だけじゃなく)に対しても機能するようにすればいい。そうすれば、この内容をHTTPに組み込んで、すべての標準ライブラリやユーティリティ、コードに反映させられるから、同じ同期ロジックをWebhookで何度も実装し直す必要がなくなるよ。興味があれば連絡してね!

メールで連絡したよ :) ただ一つ訂正があるんだ。私の仕様は/scroll/ URLを強制するわけじゃなくて、ただの慣習として提案してるだけなんだ。

Hacker Newsで議論の続きを見る