ChatWorkのWebhookから送信されるリクエストをLambdaで検証してみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

11月1日にChatWorkがWebhookとOAuthに対応し、オープンβ版の提供が開始されたという発表がありました。

ビジネスチャット「チャットワーク」がWebhookとOAuthに対応、オープンβ版の提供開始〜2018年春までにクラウドサービス17社18サービスとデータ連携開始予定〜

Webhookがあったら夢が広がるなと思っていたところのオープンβ版の提供、非常にありがたいです。

Webhook

ざっくりいうとアプリケーションに対して「何かしたら、それをトリガーに指定したURLにリクエストを送れる機能」です。 今回の場合はメッセージの送信などチャットワークに対するユーザーのアクションとなります。

ChatWork Webhookは、参加するチャットルーム(グループチャット、ダイレクトチャット、マイチャット)での、メッセージの送信・編集、自分へのメンションといったイベントを、ユーザーが設定したWebhook URLにリアルタイムに通知する機能です。 Webhook - チャットワークAPIドキュメント

これまではChatWorkへのメッセージ投稿をトリガーに動くような処理が欲しい場合、

  1. 誰かが定期的にメッセージを見にいく
  2. 新着メッセージがあったらXXXXする

といった流れの実装が必要でしたが、なんども定期的にリクエストする必要があり、リクエスト制限をどうするかなどの問題がありました。

Webhookによって、

  1. ChatWorkにメッセージを投稿
  2. Webhookによって特定のURLにリクエストが送信される
  3. リクエストを受け取ったエンドポイントがXXXXする

といった実装が可能となります。

この時、Webhookのリクエスト送信先はエンドポイントを解放する必要がありますが、ChatWorkのWebhook以外から来たリクエストを処理したくありません。 「本当にChatWorkから来た、処理しても良いリクエストか?」を検証する必要があります。

Webhookのドキュメントに「リクエストの署名検証」方法が記載されていたので、AWS Lambdaでこれを実装してみました。

Webhook - チャットワークAPIドキュメント

やってみた

構成

スクリーンショット 2017-11-02 15.12.24

  1. ChatWorkにメッセージを投稿
  2. Webhookで指定したURL(API Gatewayのエンドポイント)にリクエスト
  3. API GatewayからLambdaをキック
  4. Lambdaでリクエストを検証し、2で指定したWebhookからの送信であることが確認できれば200、できなければ403をレスポンス

手順

Lambdaの設定

検証用のLambdaの設定をします。

以下のソースコードが動作するNode.jsのLambdaを設定します。

exports.handler = function(event, context, callback) {        
    console.log('Received event:', JSON.stringify(event, null, 2));
    // Retrieve request parameters from the Lambda function input:
    var signature = event.headers['X-ChatWorkWebhookSignature'];
    var body = event.body;

    if (validate(signature, body)) {
        // Webhookで行いたい処理
        callback(null, {"statusCode": 200, "body": "Success"})
    }  else {
        callback(null, {"statusCode": 403, "body": "Forbidden"})    }
}

var validate = function(signature, body){
    var cipheredBody = digest(body);
    return cipheredBody === signature;
}

var digest = function(value){
    var secret = decode(process.env.TOKEN);
    var crypto = require('crypto');
    var hash = crypto.createHmac('SHA256', secret).update(value).digest('base64');
    return hash;
}

var encode = function(value){
    var buffer = new Buffer(value);
    var encoded = buffer.toString('base64');
    return encoded;
}

var decode = function(value){
    var buffer = new Buffer(value, 'base64');
    return buffer;
}

API Gatewayの設定

Webhookのリクエストの送信先となるAPIを作成します。

APIを作成し、POSTメソッドをセットアップします。

  • 統合タイプ:Lambda関数
  • Lambdaプロキシの使用:チェックあり
  • Lambdaリージョン:Lambda関数を作成したリージョン
  • Lambda関数:作成したLambda関数

スクリーンショット 2017-11-03 17.24.44

APIをデプロイしたら、Webhookからのリクエストの検証結果を確認するためにログの設定をします。 CloudWatchログを有効化し、"リクエスト/レスポンスをすべてログ"にチェックを入れます。

スクリーンショット 2017-11-03 17.52.53

Webhookの設定

Webhookの設定をします。

ChatWork右上の自分のアイコンをクリックし、"API設定"を開きます。 スクリーンショット 2017-11-03 17.30.32

"Webhook"→"新規作成"で新しくWebhookを定義します。 スクリーンショット 2017-11-03 17.33.12

URLにAPI Gatewayで設定したエンドポイントを指定します。

今回はリクエストの検証を行いたいだけなので、専用のルームを作成しそのルームの中のメッセージ作成をトリガーに動くWebhookを設定しました。

スクリーンショット 2017-11-03 17.36.41

設定が完了するとトークンが表示されるのでメモしておきます。

Lambdaにトークンを設定

最初に設定したLambda関数の環境変数を指定します。

スクリーンショット 2017-11-02 20.24.40

動かしてみる

Webhookを指定したChatWorkのルームにメッセージを投稿します。

CloudWatchのログで、Webhookからエンドポイントを実行し、リクエストの検証結果としてステータスコード200が返却されていることを確認できます。 スクリーンショット 2017-11-03 17.56.12

検証に失敗するパターンを試すため、Lambdaに指定したトークンに誤った値を指定し、再度Webhookからリクエストを送信してみます。

リクエストの検証に失敗し、ステータスコード403が返却されていることが確認できました。 スクリーンショット 2017-11-03 18.02.53

まとめ

ChatWorkのWebhookの提供により、ChatWorkに対するアクションをトリガーに動く処理を作成しやすくなりました。 本記事ではWebhookからLambdaの処理を実行するために、Webhookからのリクエストであることを検証するための設定をしてみました。

ここまでの設定はあくまでWebhookを利用する前準備なので、これから連携後の処理を何か書いてみようと思います。

私からは以上です。

参考