コールバックとは、コールバックとトリガーの違い と書いてきて、最後に残ったのが Webhook です。この 3 つの言葉は、少しずつ重なっていて、混ざりやすいものです。この記事では、Webhook をその 2 つの言葉で説明し、作るとき・受けるときに気をつけることをまとめます。
ひとことで
Webhook は、「○○が起きたら(トリガー)、前もって登録しておいた URL へ(コールバック)、相手のサービスが HTTP で知らせてくる」仕組みです。
| 部品 | Webhook では | 例(GitHub) |
|---|---|---|
| トリガー(きっかけ) | 相手のサービスで起きた出来事(イベント) | push された、プルリクエストが作られた |
| コールバック(連絡先) | こちらが前もって登録した受け口の URL | https://ci.example.com/hooks/github |
| 知らせ方 | 相手のサーバーから、こちらのサーバーへの HTTP の POST(中身は JSON が多い) | push の内容(ブランチ、コミットなど) |
つまり Webhook は、新しい考え方ではありません。トリガーとコールバックを、サービスどうしの HTTP の上で組み合わせたものです。
API との違い:向きが逆
| ふつうの API | Webhook | |
|---|---|---|
| 誰が始めるか | こちらから相手に聞きに行く | 相手からこちらに知らせてくる |
| たとえ | 「何か変わりましたか?」と電話する | 「変わったら電話します」と言われて待つ |
| 変化に気づくには | 何度も聞きに行く(ポーリング) | 待っていれば来る |
この向きの逆さから、Webhook は「逆向きの API(reverse API)」と呼ばれることもあります。ポーリングと比べた良い点・弱い点は、コールバックの記事 の比較の表と同じです。
流れ
- 登録する:相手のサービスの設定画面や API で、受け口の URL・知らせてほしい出来事・合言葉(シークレット)を登録する
- 出来事が起きる:相手のサービスで、登録した出来事が起きる(トリガー)
- 知らせが来る:相手のサーバーが、受け口の URL に HTTP の POST を送る(コールバック)。シークレットで作った署名をヘッダーに付ける
- 受け取る:こちらは署名を確かめ、すぐに
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