HTTP / WebSpec

Webhook とは:トリガーとコールバックを、サービスどうしの HTTP で組み合わせたもの

コールバックとは、コールバックとトリガーの違い と書いてきて、最後に残ったのが Webhook です。この 3 つの言葉は、少しずつ重なっていて、混ざりやすいものです。この記事では、Webhook をその 2 つの言葉で説明し、作るとき・受けるときに気をつけることをまとめます。

ひとことで

Webhook は、「○○が起きたら(トリガー)、前もって登録しておいた URL へ(コールバック)、相手のサービスが HTTP で知らせてくる」仕組みです。

部品Webhook では例(GitHub)
トリガー(きっかけ)相手のサービスで起きた出来事(イベント)push された、プルリクエストが作られた
コールバック(連絡先)こちらが前もって登録した受け口の URLhttps://ci.example.com/hooks/github
知らせ方相手のサーバーから、こちらのサーバーへの HTTP の POST(中身は JSON が多い)push の内容(ブランチ、コミットなど)

つまり Webhook は、新しい考え方ではありません。トリガーとコールバックを、サービスどうしの HTTP の上で組み合わせたものです。

API との違い:向きが逆

ふつうの APIWebhook
誰が始めるかこちらから相手に聞きに行く相手からこちらに知らせてくる
たとえ「何か変わりましたか?」と電話する「変わったら電話します」と言われて待つ
変化に気づくには何度も聞きに行く(ポーリング)待っていれば来る

この向きの逆さから、Webhook は「逆向きの API(reverse API)」と呼ばれることもあります。ポーリングと比べた良い点・弱い点は、コールバックの記事 の比較の表と同じです。

流れ

  1. 登録する:相手のサービスの設定画面や API で、受け口の URL・知らせてほしい出来事・合言葉(シークレット)を登録する
  2. 出来事が起きる:相手のサービスで、登録した出来事が起きる(トリガー)
  3. 知らせが来る:相手のサーバーが、受け口の URL に HTTP の POST を送る(コールバック)。シークレットで作った署名をヘッダーに付ける
  4. 受け取る:こちらは署名を確かめ、すぐに 200 などを返す。重い処理は、返したあとで行う

受ける側が気をつけること

コールバックの記事に書いた「連絡先を前もって登録する」「知らせてきた相手を確かめる」の 2 点に加えて、Webhook ならではの注意があります。

1. 署名を確かめる

受け口の URL は、インターネットから誰でも POST できます。偽の知らせを受けないように、署名を確かめます。多くのサービスは、登録したシークレットと本文から HMAC-SHA256 で署名を作り、ヘッダーに付けてきます。GitHub の場合は、X-Hub-Signature-256 ヘッダーに sha256=<16進の署名> が入っています。

import hashlib
import hmac


def verify_github_signature(secret: bytes, body: bytes, header: str) -> bool:
    """GitHub の Webhook の署名を確かめる。body は JSON に直す前の、受け取ったままのバイト列。"""
    expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")
  • 本文は、JSON に直す前の、受け取ったままのバイト列で計算する。一度 JSON に直してから文字列に戻すと、空白や順番が変わって署名が合わない
  • 比べるときは == ではなく hmac.compare_digest を使う(比べるのにかかる時間から、署名を推測されないため)
  • ヘッダーの名前や署名の作り方は、サービスごとに違う。使うサービスの説明を確かめる
2. すぐに返す

相手は、返事を長くは待ちません(GitHub は 10 秒ほど)。時間がかかると「失敗した」と見なされます。受け取ったらすぐに 200 などを返し、重い処理は、キューに入れるなどして後で行います。

3. 同じ知らせが 2 回来てもよいようにする

失敗と見なされると、同じ知らせを送り直すサービスが多くあります(Stripe など)。届いていたのに返事が遅れただけでも、送り直されます。知らせに付いている出来事の ID を覚えておき、処理済みなら何もしないようにします。

4. 順番どおりに来るとは限らない

「作られた」より先に「更新された」が届くこともあります。順番に頼る処理にしないか、知らせの中の時刻で確かめます。

5. 受け口の確認に答える

受け口を登録したときに、相手が「この URL は本当にあなたのものか」を確かめに来るサービスがあります。

  • Microsoft Graph の変更通知:validationToken を付けて呼んでくるので、その値をそのまま返す
  • Slack の Events API:challenge の値をそのまま返す
6. 相手から届く場所に置く

Webhook は相手のサーバーから、こちらへ届く必要があります。クラウドのサービスの Webhook を、社内のネットワークの中のサーバーで受けたいときは、そのままでは届きません。インターネットから届く受け口を用意するか、Webhook をあきらめてポーリングにするかを選ぶことになります。

ややこしい言葉:Incoming Webhook

Slack・Mattermost・Teams などのチャットツールには、「Incoming Webhook」という機能があります。これは、ここまでの Webhook とは向きが逆です。

名前向き中身
Outgoing Webhook(ここまでの Webhook)チャットツール → こちらの URL「メッセージが投稿された」などを、こちらに知らせてくる
Incoming Webhookこちら → チャットツールの URLチャットツールが用意した URL に、こちらから POST してメッセージを書き込む

Incoming Webhook は、チャットツールから見て「入ってくる」という意味の名前です。実際には、秘密の URL を鍵にした、書き込み用の API です。こちらから呼ぶだけで、トリガーもコールバックもありません。その URL を知っている人は誰でも書き込めるので、URL をパスワードと同じように扱います。

まとめ

  • Webhook は「出来事が起きたら(トリガー)、登録しておいた URL へ(コールバック)、相手のサービスが HTTP で知らせてくる」仕組み
  • ふつうの API とは向きが逆で、ポーリングしなくても変化がわかる
  • 受ける側は、署名を確かめる、すぐ返す、2 回来てもよいようにする、順番に頼らない、確認に答える、相手から届く場所に置く
  • チャットツールの「Incoming Webhook」は向きが逆で、実体は秘密の URL を使った書き込み用の API
スポンサーリンク
タイトルとURLをコピーしました