Skip to main content

webhookの配信の取り扱い

Webhook 配信をリッスンして応答するコードを記述する方法について説明します。

はじめに

Webhook を作成する際は、URL を指定し、イベントの種類をサブスクライブします。 Webhook がサブスクライブされているイベントが発生すると、GitHub は、指定した URL にイベントに関するデータを含む HTTP 要求を送信します。 その URL で Webhook 配信をリッスンするようにサーバーが設定されている場合は、受信したときにアクションを実行できます。

この記事では、サーバーが Webhook 配信をリッスンして応答できるようにコードを記述する方法について説明します。 ローカル サーバーとしてコンピューターまたは codespace を使用して、コードをテストします。

段取り

Webhook をローカルでテストするのに、Webhook プロキシ URL を使用して、GitHub から自分のコンピューターまたは codespace に Webhook を転送することができます。 この記事では、smee.io を使用して Webhook プロキシ URL を指定し、Webhook を転送します。

Webhook プロキシ URL を取得する

  1. ブラウザーで https://smee.io/ にアクセスします。
  2. [Start a new channel] (新しいチャネルの開始) をクリックします。
  3. [Webhook Proxy URL] (Webhook プロキシ URL) の下にある完全な URL をコピーします。 この URL は次の設定ステップで使用します。

Webhook の転送

  1. smeeクライアント がまだインストールされていない場合は、ターミナルで次のコマンドを実行します。

    Shell
    npm install --global smee-client
    
  2. smee.io から転送された Webhook を受信するには、ターミナルで次のコマンドを実行します。 WEBHOOK_PROXY_URL を、以前の Webhook プロキシ URL に置き換えます。

    Shell
    smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000
    

    次のような出力が表示されます。WEBHOOK_PROXY_URL は Webhook プロキシ URL です。

    Shell
    Forwarding WEBHOOK_PROXY_URL to http://127.0.0.1:3000/webhook
    Connected WEBHOOK_PROXY_URL
    

    パスが /webhook でポートが 3000 であることに注意してください。 これらの値は、後で Webhook 配信を処理するコードを記述するときに使用します。

  3. Webhook のテスト中は、これを実行し続けます。 Webhook の転送を停止する場合は、 Ctrl+C を押します。

webhook を作成する

  1. 次の設定を使用して Webhook を作成します。 詳しくは、「webhookの作成」を参照してください。

    • URL には、前の Webhook プロキシ URL を使用します。
    • コンテンツ タイプを選択するオプションがある場合は、JSON を使用します。

Webhook の配信を処理するコードを記述

Webhook の配信を処理するには、次のコードを記述する必要があります。

  • Webhook URL への要求をリッスンするようにサーバーを初期化
  • 要求から HTTP ヘッダーと本文を読み取る
  • 要求に応答して目的のアクションを実行

サーバーで実行できる任意のプログラミング言語を使用できます。

次の例では、Webhook 配信を受信したときにメッセージを出します。 しかし、GitHub API への要求や Slack メッセージの送信など、別のアクションを実行するようにコードを変更できます。

Ruby の例

この例では、Ruby gem の Sinatra を使用してルートを定義し、HTTP 要求を処理します。 詳しくは、Sinatra の README をご覧ください。

Ruby の例: 依存関係のインストール

この例を使用するには、Ruby プロジェクトに Sinatra gem をインストールする必要があります。 たとえば、これは Bundler を使って行うことができます。

  1. Bundler がまだインストールされていない場合は、ターミナルで次のコマンドを実行します。

    Shell
    gem install bundler
    
  2. アプリに Gemfile がまだない場合は、ターミナルで次のコマンドを実行します。

    Shell
    bundle init
    
  3. アプリに Gemfile.lock がまだない場合は、ターミナルで次のコマンドを実行します。

    Shell
    bundle install
    
  4. ターミナルで次のコマンドを実行して Sinatra gem をインストールします。

    Shell
    bundle add sinatra
    

Ruby の例: コードの記述

次のような内容の Ruby ファイルを作成します。 Webhook がサブスクライブしているイベントの種類と、Webhook の作成時に GitHub が送信する ping イベントを処理するようにコードを変更します。 この例では issues イベントと ping イベントを処理します。

Ruby
require 'sinatra'
require 'json'

These are the dependencies for this code. You installed the sinatra gem earlier. For more information, see "Ruby example: Install dependencies." The json library is a standard Ruby library, so you don't need to install it.

post '/webhook' do

The /webhook route matches the path that you specified for the smee.io forwarding. For more information, see "Forward webhooks."

Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.

  status 202

Respond to indicate that the delivery was successfully received. Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.

  github_event = request.env['HTTP_X_GITHUB_EVENT']

Check the X-GitHub-Event header to learn what event type was sent. Sinatra changes X-GitHub-Event to HTTP_X_GITHUB_EVENT.

  if github_event == "issues"
    data = JSON.parse(request.body.read)
    action = data['action']
    if action == "opened"
      puts "An issue was opened with this title: #{data['issue']['title']}"
    elsif action == "closed"
      puts "An issue was closed by #{data['issue']['user']['login']}"
    else
      puts "Unhandled action for the issue event: #{action}"
    end
  elsif github_event == "ping"
    puts "GitHub sent the ping event"
  else
    puts "Unhandled event: #{github_event}"
  end
end

You should add logic to handle each event type that your webhook is subscribed to. For example, this code handles the issues and ping events.

If any events have an action field, you should also add logic to handle each action that you are interested in. For example, this code handles the opened and closed actions for the issue event.

For more information about the data that you can expect for each event type, see "Webhook のイベントとペイロード."

# These are the dependencies for this code. You installed the `sinatra` gem earlier. For more information, see "[Ruby example: Install dependencies](#ruby-example-install-dependencies)." The `json` library is a standard Ruby library, so you don't need to install it.
require 'sinatra'
require 'json'

# The `/webhook` route matches the path that you specified for the smee.io forwarding. For more information, see "[Forward webhooks](#forward-webhooks)."
#
# Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.
post '/webhook' do

  # Respond to indicate that the delivery was successfully received.
  # Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.
  status 202

  # Check the `X-GitHub-Event` header to learn what event type was sent.
  # Sinatra changes `X-GitHub-Event` to `HTTP_X_GITHUB_EVENT`.
  github_event = request.env['HTTP_X_GITHUB_EVENT']

  # You should add logic to handle each event type that your webhook is subscribed to.
  # For example, this code handles the `issues` and `ping` events.
  #
  # If any events have an `action` field, you should also add logic to handle each action that you are interested in.
  # For example, this code handles the `opened` and `closed` actions for the `issue` event.
  #
  # For more information about the data that you can expect for each event type, see "[AUTOTITLE](/webhooks/webhook-events-and-payloads)."
  if github_event == "issues"
    data = JSON.parse(request.body.read)
    action = data['action']
    if action == "opened"
      puts "An issue was opened with this title: #{data['issue']['title']}"
    elsif action == "closed"
      puts "An issue was closed by #{data['issue']['user']['login']}"
    else
      puts "Unhandled action for the issue event: #{action}"
    end
  elsif github_event == "ping"
    puts "GitHub sent the ping event"
  else
    puts "Unhandled event: #{github_event}"
  end
end

Ruby の例: コードのテスト

Webhook をテストするのに、コンピューターまたは codespace をローカル サーバーとして機能するように使用できます。 これらのステップで問題が生じた場合は、「トラブルシューティング」を参照してください。

  1. Webhook が転送されていることを確認します。 Webhook が転送されなくなった場合は、「Webhook の転送」のステップをもう一度実行します。

  2. 別のターミナル ウィンドウで、次のコマンドを実行して、コンピューターまたは codespace でローカル サーバーを起動します。 FILE_PATH を前のセクションのコードが格納されているファイルへのパスに置き換えます。 前のステップで Webhook 転送に指定したポートと PORT=3000 が一致していることに注意してください。

    Shell
    PORT=3000 ruby FILE_NAME
    

    "Sinatra has taken the stage on 3000" といった出力が表示されるはずです。

  3. Webhook をトリガーします。 たとえば、issues イベントをサブスクライブするリポジトリ Webhook を作成した場合は、リポジトリでイシューを開きます。 以前の Webhook 配信を再配信することもできます。 詳しくは、「Webhook の再配信」を参照してください。

  4. smee.io の Webhook プロキシ URL に移動します。 トリガーまたは再配信したイベントに対応するイベントが表示されるはずです。 これは、GitHub が、指定したペイロード URL に Webhook 配信を正常に送信したことを表します。

  5. smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000 を実行したターミナル ウィンドウに、POST http://127.0.0.1:3000/webhook - 202 といった表示が出るはずです。 これは、smee が Webhook をローカル サーバーに正常に転送したことを表します。

  6. PORT=3000 ruby FILE_NAME を実行したターミナル ウィンドウに、送信されたイベントに対応するメッセージが表示されるはずです。 たとえば、上記のコード例を使用して ping イベントを再配信した場合は、"GitHub sent the ping event" と表示されるはずです。 また、Sinatra が自動的に出力する他の文も表示される場合もあります。

  7. 両方のターミナル ウィンドウで、Ctrl+C を押してローカル サーバーを停止し、転送された Webhook のリッスンを停止します。

コードをローカルでテストしたら、運用環境で Webhook を使用するように変更を加えることができます。 詳細については、「次のステップ」を参照してください。 コードのテストで問題が発生した場合は、「トラブルシューティング」の手順を試してください。

JavaScript の例

この例では、Node.js と Express ライブラリを使用してルートを定義し、HTTP 要求を処理します。 詳細については、「expressjs.com」を参照してください。

の Octokit.js SDK GitHub データを使用する例については、「Webhook イベントに応答する GitHub App の構築」を参照してください。

この例では、お使いのコンピューターまたは codespace で Node.js バージョン 12 以降と npm バージョン 6.12.0 以降が実行されている必要があります。 詳しくは、Node.js のページを参照してください。

JavaScript の例: 依存関係のインストール

この例を使用するには、Node.js プロジェクトに express ライブラリをインストールする必要があります。 次に例を示します。

Shell
npm install express

JavaScript の例: コードを記述する

次の内容を持つJavaScriptファイル を作成します。 Webhook がサブスクライブしているイベントの種類と、Webhook の作成時に GitHub が送信する ping イベントを処理するようにコードを変更します。 この例では issues イベントと ping イベントを処理します。

JavaScript
const express = require('express');

You installed the express library earlier. For more information, see "JavaScript example: Install dependencies."

const app = express();

This initializes a new Express application.

app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {

This defines a POST route at the /webhook path. This path matches the path that you specified for the smee.io forwarding. For more information, see "Forward webhooks."

Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.

  response.status(202).send('Accepted');

Respond to indicate that the delivery was successfully received. Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.

  const githubEvent = request.headers['x-github-event'];

Check the x-github-event header to learn what event type was sent.

  if (githubEvent === 'issues') {
    const data = request.body;
    const action = data.action;
    if (action === 'opened') {
      console.log(`An issue was opened with this title: ${data.issue.title}`);
    } else if (action === 'closed') {
      console.log(`An issue was closed by ${data.issue.user.login}`);
    } else {
      console.log(`Unhandled action for the issue event: ${action}`);
    }
  } else if (githubEvent === 'ping') {
    console.log('GitHub sent the ping event');
  } else {
    console.log(`Unhandled event: ${githubEvent}`);
  }
});

You should add logic to handle each event type that your webhook is subscribed to. For example, this code handles the issues and ping events.

If any events have an action field, you should also add logic to handle each action that you are interested in. For example, this code handles the opened and closed actions for the issue event.

For more information about the data that you can expect for each event type, see "Webhook のイベントとペイロード."

const port = 3000;

This defines the port where your server should listen. 3000 matches the port that you specified for webhook forwarding. For more information, see "Forward webhooks."

Once you deploy your code to a server, you should change this to match the port where your server is listening.

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

This starts the server and tells it to listen at the specified port.

// You installed the `express` library earlier. For more information, see "[JavaScript example: Install dependencies](#javascript-example-install-dependencies)."
const express = require('express');

// This initializes a new Express application.
const app = express();

// This defines a POST route at the `/webhook` path. This path matches the path that you specified for the smee.io forwarding. For more information, see "[Forward webhooks](#forward-webhooks)."
//
// Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.
app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {

  // Respond to indicate that the delivery was successfully received.
  // Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.
  response.status(202).send('Accepted');

  // Check the `x-github-event` header to learn what event type was sent.
  const githubEvent = request.headers['x-github-event'];

  // You should add logic to handle each event type that your webhook is subscribed to.
  // For example, this code handles the `issues` and `ping` events.
  //
  // If any events have an `action` field, you should also add logic to handle each action that you are interested in.
  // For example, this code handles the `opened` and `closed` actions for the `issue` event.
  //
  // For more information about the data that you can expect for each event type, see "[AUTOTITLE](/webhooks/webhook-events-and-payloads)."
  if (githubEvent === 'issues') {
    const data = request.body;
    const action = data.action;
    if (action === 'opened') {
      console.log(`An issue was opened with this title: ${data.issue.title}`);
    } else if (action === 'closed') {
      console.log(`An issue was closed by ${data.issue.user.login}`);
    } else {
      console.log(`Unhandled action for the issue event: ${action}`);
    }
  } else if (githubEvent === 'ping') {
    console.log('GitHub sent the ping event');
  } else {
    console.log(`Unhandled event: ${githubEvent}`);
  }
});

// This defines the port where your server should listen.
// 3000 matches the port that you specified for webhook forwarding. For more information, see "[Forward webhooks](#forward-webhooks)."
//
// Once you deploy your code to a server, you should change this to match the port where your server is listening.
const port = 3000;

// This starts the server and tells it to listen at the specified port.
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

JavaScript の例: コードをテストする

Webhook をテストするのに、コンピューターまたは codespace をローカル サーバーとして機能するように使用できます。 これらのステップで問題が生じた場合は、「トラブルシューティング」を参照してください。

  1. Webhook が転送されていることを確認します。 Webhook が転送されなくなった場合は、「Webhook の転送」のステップをもう一度実行します。

  2. 別のターミナル ウィンドウで、次のコマンドを実行して、コンピューターまたは codespace でローカル サーバーを起動します。 FILE_PATH を前のセクションのコードが格納されているファイルへのパスに置き換えます。

    Shell
    node FILE_NAME
    

    Server is running on port 3000という出力が表示されます。

  3. Webhook をトリガーします。 たとえば、issues イベントをサブスクライブするリポジトリ Webhook を作成した場合は、リポジトリでイシューを開きます。 以前の Webhook 配信を再配信することもできます。 詳しくは、「Webhook の再配信」を参照してください。

  4. smee.io の Webhook プロキシ URL に移動します。 トリガーまたは再配信したイベントに対応するイベントが表示されるはずです。 これは、GitHub が、指定したペイロード URL に Webhook 配信を正常に送信したことを表します。

  5. smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000 を実行したターミナル ウィンドウに、POST http://127.0.0.1:3000/webhook - 202 といった表示が出るはずです。 これは、smee が Webhook をローカル サーバーに正常に転送したことを表します。

  6. node FILE_NAME を実行したターミナル ウィンドウに、送信されたイベントに対応するメッセージが表示されるはずです。 たとえば、上記のコード例を使用して ping イベントを再配信した場合は、"GitHub sent the ping event" と表示されるはずです。

  7. 両方のターミナル ウィンドウで、Ctrl+C を押してローカル サーバーを停止し、転送された Webhook のリッスンを停止します。

コードをローカルでテストしたら、運用環境で Webhook を使用するように変更を加えることができます。 詳細については、「次のステップ」を参照してください。 コードのテストで問題が発生した場合は、「トラブルシューティング」の手順を試してください。

トラブルシューティング

テスト ステップで説明された予想される結果が表示されない場合は、次の試してください。

  • Webhook が Webhook プロキシ URL (Smee.io URL) を使用していることを確認します。 Webhook プロキシ URL の詳細については、「Webhook プロキシ URL の取得」を参照してください。 Webhook 設定の詳細については、「webhookの作成」を参照してください。
  • 使用するコンテンツ タイプを選択できる場合は、Webhook で JSON コンテンツ タイプが使用されていることを確認します。 Webhook 設定の詳細については、「webhookの作成」を参照してください。
  • smee クライアントとローカル サーバーの両方が動作していることを確認します。 これらのプロセスは、2 つの個別のターミナル ウィンドウで実行されます。
  • サーバーが、smee.io が Webhook を転送しているのと同じポートをリッスンしていることを確認します。 この記事のすべての例では、ポート3000を使用しています。
  • smee.io が webhook を転送しているパスが、コードで定義されているルートと一致していることを確認します。 この記事のすべての例では、/webhooks パスを使用します。
  • smee クライアントとローカル サーバーを実行しているターミナル ウィンドウでエラー メッセージを確認します。
  • GitHub をチェックして、Webhook 配信がトリガーされたことを確認します。 詳しくは、「webhookの配信の表示」を参照してください。
  • smee.io の Webhook プロキシ URL を確認します。 トリガーまたは再配信したイベントに対応するイベントが表示されるはずです。 これは、GitHub が、指定したペイロード URL に Webhook 配信を正常に送信したことを表します。

次のステップ

この記事では、Webhook 配信を処理するコードを記述する方法を示しました。 また、コンピューターまたは codespace をローカル サーバーとして使用し、GitHub から smee.io 経由でローカル サーバーに Webhook 配信を転送することで、コードをテストする方法についても説明しました。 コードのテストが完了したら、コードを変更し、サーバーにコードをデプロイすることができます。

コードを変更する

この記事では、Webhook 配信を受信したときにメッセージを出力する基本的な例を示しました。 コードを変更して、他のアクションを実行することもできます。 たとえば、次を実行するようにコードを変更できます。

  • GitHub API への要求を行う
  • Slack でメッセージを送信
  • ログ イベント
  • 外部プロジェクト管理ツールの更新

配信が GitHub からであることの検証

Webhook 配信を処理するコードでは、配信をさらに処理する前に、配信が GitHub からであることを検証する必要があります。 詳しくは、「Webhook 配信を検証する」を参照してください。

コードをサーバーにデプロイする

この記事では、コードの開発時にコンピューターまたは codespace をサーバーとして使用する方法について説明しました。 コードを運用環境で使用する準備ができたら、コードを専用サーバーにデプロイする必要があります。

その場合は、サーバーがリッスンしているホストとポートを反映するようにコードを更新する必要があります。

Webhook URL を更新する

GitHub から Webhook トラフィックを受信するようにサーバーを設定したら、Webhook 設定の URL を更新します。 新しい URL のパス部分と一致するように、コードが処理するルートを更新する必要がある場合があります。 たとえば、新しい webhook URL がhttps://example.com/github-webhooksである場合、これらの例のルートを /webhooks から/github-webhooks に変更する必要があります。

運用環境では Webhook を転送するのに smee.io を使用しないでください。

ベスト プラクティスに従う

Webhook についてはベスト プラクティスに従うようにする必要があります。 詳しくは、「Webhook の使用に関するベスト プラクティス」を参照してください。

参考資料