Webhookとは、サービスと API の間の通信を可能にする仕組みです。これらの通信の橋渡し役として、さまざまな要素のつながり合うクラウドベース アプリケーション環境を実現します。API をご存知の方なら、Webhook の使い方はすぐに覚えられます。この度、CircleCI では、CircleCI の CI/CDツールに対応した Webhook 機能をリリースしました。これにより、ワークフローやジョブの完了など、CircleCI のイベントをサブスクライブし対応できるようになります。

このチュートリアルでは、Webhook について解説した後、使用方法について順を追って説明します。なお、想定読者は、既に CircleCI を使用してプロジェクトをビルドしている方です。また、本記事に合わせ、 サンプルの NodeJS Webhook コンシューマー プロジェクトを無料で公開しています。

Webhook と API の相違点

Webhook と API の一番の違いは、開発者から見れば API の役割はプルであるのに対し、Webhook の役割はプッシュである点です。API は、クライアントのデータの要求先です。クライアントは、データがさらに必要な場合、またはデータの更新の有無を確認する必要がある場合に、別のリクエストを作成します。Webhook では、サービス (クライアント) を Webhook のエンドポイントとして設定し、このエンドポイントを別のサービスに提供することになります。そして、この “別のサービス” (この記事では CircleCI) は、対象のイベント発生時に Webhook エンドポイントを呼び出します。Webhook は、GitHub のインテグレーションや Discord のチャット ボット、Slack の通知など、さまざまな形で使用されており、その可能性は莫大なものです。

CircleCI では、プロジェクトのワークフローおよびジョブの完了イベントを通知する Webhook を設定できます。これらのイベントは、ワークフローやジョブの成否を問わず、それらが完了した時点でトリガーされます。

Webhook なしでこのような仕組みを実現するなら、CircleCI APIを繰り返しポーリングするしかありません。このように API を使う場合、サービスは最新のデータを取得するために、リクエストを何度も作成する必要があります。この方法は、開発チームからすれば好ましくありません。毎日 (数千件とはいかずとも) 数百件のリクエストが作成されるサービスを保守しなければならないからです。また、CircleCI の立場では、数千件に上るリクエストを処理する必要が出てくるため、API リクエストを繰り返し行う方法はやはり良いものではありません。

Webhook で実現できるユース ケース

CI/CD の観点では、Webhook には、ダッシュボード、データのログ、プロジェクト管理ツールなど、あらゆる種類の連携の仕組みを構築できるという効果があります。たとえば、ダウンストリームのクライアントに対して通知を自動で送信したり、アップデートが必要な新バージョンを知らせたりすることができます。Webhook は、単一のプロジェクトだけでなく、複数のプロジェクトにも設定できます。後者の場合、各プロジェクトのイベントを集約し、ユース ケースに応じた方法によりバックエンドで処理を行えます。

CircleCI Webhook の設定ステップ

CircleCI の Web インターフェイスで新しい Webhook を個々のプロジェクトに設定するには、該当する CircleCI プロジェクトのプロジェクト設定に移動します。[Webhook] セクションを選択し、[Add Webhook (Webhook の追加)] ボタンをクリックします。

Setting up a webhook interface

Webhook の作成時に指定できるオプションは次のとおりです。

  • 名前
  • Webhook の送信先エンドポイントの URL
  • 送信対象のイベント
  • 証明書を検証するかどうか
  • シークレットの値

上図の例では、ローカル サーバーからの ngrok のトンネルで生成された一時 URL を使用し、シークレット トークンには super-secret-1234を指定しています。これらは要点を解説するための例ですので、実際のシークレットとは異なります。

[Add Webhook (Webhook の追加)] ボタンをクリックすると、Webhook が有効になります。これで、[URL] フィールドに入力したエンドポイントに対し、CircleCI からワークフローのすべての完了イベントが送信されます。

ペイロードの処理と Webhook への応答

Webhook は、設定時に指定したエンドポイントに対して、POST リクエストボディとしてペイロードを送信します。

Webhook を処理するサーバーは、応答としてステータス コード 200 が返す必要があります。そうでないと、Webhook が何度も送信され、エンドポイントでイベントが重複しかねません。すべての受信 Webhook のペイロードを RabbitMQ や Amazon SQS などのメッセージ キューに渡し、非同期的に処理することをお勧めします。

Webhook のペイロードの内容

ペイロードには、完了したワークフローの名前、ステータス、タイミング、プロジェクトやパイプライン、関連付けられているコミット、パイプラインの実行を開始させたトリガーなどの情報が含まれています。パイプライン トリガーの値は、VCS コミット リクエストの場合には webhookです。他の場合は、apischeduleのいずれかになります。

ワークフローの全ペイロードの例を次に示します。

{
  type: 'workflow-completed',
  id: 'f85a2fd5-108d-38e2-a990-fb49e7001515',
  happened_at: '2021-06-01T11:23:14.146Z',
  webhook: {
    id: '1154a973-e434-407c-8051-9259d8a53e99',
    name: 'Workflow Completed'
  },
  workflow: {
    id: '89b02ea5-7fca-4017-a902-747e68235ffd',
    name: 'do-all-the-things',
    status: 'success',
    created_at: '2021-06-01T11:23:07.441Z',
    stopped_at: '2021-06-01T11:23:13.942Z',
    url: 'https://app.circleci.com/pipelines/github/zmarkan/very-simple-non-app/26/workflows/89b02ea5-7fca-4017-a902-747e68235ffd'
  },
  pipeline: {
    id: 'ac0cc08f-67a2-4adb-9fa6-db633e390670',
    number: 26,
    created_at: '2021-06-01T11:23:07.371Z',
    trigger: { type: 'webhook' },
    vcs: {
      provider_name: 'github',
      origin_repository_url: 'https://github.com/zmarkan/very-simple-non-app',
      target_repository_url: 'https://github.com/zmarkan/very-simple-non-app',
      revision: 'f1e37011b5ff2d1703d4de9143c52ba650acae53',
      commit: [Object],
      branch: 'streamline'
    }
  },
  project: {
    id: 'e5c5dce3-7ef8-4227-9888-6b2b97b0a5ab',
    name: 'very-simple-non-app',
    slug: 'github/zmarkan/very-simple-non-app'
  },
  organization: { id: '8995ddc0-b07a-4bc2-8eb6-816c67a512fa', name: 'zmarkan' }
}

Webhook 内のオブジェクトにはすべて、一意の id フィールドがあります。CircleCI からさらに情報を取得したい場合は、この id を使用し CircleCI API に対してクエリを実行できます。

Webhook の署名の検証

Webhook エンドポイントが受信するイベントは、CircleCI からのものに限りません。エンドポイントのアドレスを知っている Web 上のあらゆる人から、イベントが送信される可能性があります。リクエストの送信元を管理することはできないため、イベントの送信者が適切であるか検証し、悪意のあるリクエストを受信しないようにする必要があります。

リクエストが安全であるか確認できるように、Webhook ではシークレットの検証機能が備わっています。新しく Webhook を設定する際に、secretの値を指定することができます。 例では、super-secret-1234を使用しました。 CircleCI はこの値に基づき、Webhook リクエストの本文について HMAC SHA256 ダイジェストを作成します。シークレット ダイジェストは、Webhook ペイロードの受信時にcircleci-signature ヘッダーで渡されます。値は、パラメーター v1 に次の形式で格納されています:circleci-signature: v1=YOUR_HMAC_SHA256_DIGEST

クエストを検証するには、自身のリクエスト本文のダイジェストを同じシークレット キーとアルゴリズムで作成し、circleci-signature ヘッダーのものと比較します。両方の値が同じであれば、受信したリクエストは間違いなく CircleCI から送信されたものです。署名が一致しない場合は、悪意のある送信者からのものだと考えられます。

以下に、サンプルの NodeJS Express アプリに含まれるサンプル スニペットを示します。

// req と res は Express のオブジェクト

let signature = req.headers["circleci-signature"].substring(3)
const key = "super-secret-1234" // Same string as used in webhook setup
let testDigest = crypto.createHmac('sha256', key).update(JSON.stringify(req.body)digest('hex')

if (testDigest !== crypto.sign) {
    console.log("Webhook signature not matching")
    console.log(`Signature: ${signature}`)
    console.log(`Test digest: ${testDigest}`)
    res.status(403).send("Invalid signature")
}

Webhook を使用したダッシュボード インテグレーションの作成

Webhook の受信と処理の準備を整えたら、これによりさまざまなインテグレーションを構築できます。サンプルとして、受信したワークフローのステータスを示すダッシュボードを Vue と Express で作成しました。

Sample webhooks dashboard

このソース コードは GitHub で公開しています。Webhook のテストに使用したこのプロジェクトも、同じくGitHub で入手できます

まとめ

CircleCI Webhook は、CircleCI パイプラインを他のビジネス プロセスと連携できる強力な機能です。ダッシュボードから、プロジェクトの追跡、通知まで、情報を取得して共有するさまざまな仕組みを新たに作り出せるのです。このチュートリアルでは、ワークフローの完了時に通知する Webhook をプロジェクトに設定する方法についてご説明しました。ここからは、Webhook の処理とシステムのセキュリティ保護に関するベスト プラクティスを参考にして、チームと共に独自のプロジェクトを使用して、色々な使い方を試してみてください。

ご意見やご感想、次の記事で取り上げてほしいテーマなどがございましたら、Twitter の @zmarkanまでご連絡ください。