【Zoom 2022】Zoom の会議開始を LINEアプリにお知らせする仕組みを Zoom Webhook と enebular で作る（Webhook  のバリデーション対応も行いつつ）

## はじめに
こちらは、[Zoom の API / SDK を使ってみよう！ by Zoom Advent Calendar 2022](https://qiita.com/advent-calendar/2022/zoom) の 22日目の記事です。
（余談ですが、この記事は、自分が 2022年のアドベントカレンダー用に書いて公開した記事の中の、ちょうど 40記事目となります）

記事の内容は、Zoom Webhook を使ったものです。具体的には、Zoom のミーティング開始を [enebular](https://www.enebular.com/ja/) 経由で LINEアプリに知らせるというものです。
想定する利用シーンは、自分が参加する予定の Zoom会議が開かれた際に、スマホに会議が開始されたという通知を受けとることで、「会議の開始時間を勘違いしていた」・「会議の開始の時に別のことをしていて、会議への参加を忘れそうになった」というのを防止できるという内容です。

## 全体構成と動作している様子
### 全体構成
今回の仕組みの全体構成は、以下のとおりです。

Zoom --（Webhook）--> enebular --（LINE Notify）--> LIENアプリ（通知）

Zoom の Webhook で、LINEアプリへの通知を実現します。

### 動作している様子
今回の仕組みを動作させた様子を動画にしてみました。

iPhone上で、Zoomミーティングを開くと、その開始タイミングで LINEアプリにメッセージが来たという通知が出てくる、という内容です（LINE の通知が出るのが一瞬だったので、iMovie でカット編集をした時に、LINE通知が出る部分を意図的にスローにしています）。

●Zoomミーティングの開始時のイベントを Zoom Webhook で enebular が受け取って LINEアプリに通知（LINE Notify を利用） - YouTube
　https://www.youtube.com/watch?v=kKrPPjFyvuM

<iframe width="560" height="315" src="https://www.youtube.com/embed/kKrPPjFyvuM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Zoom と連携させたもの
### enebular
今回、Webhook を受けるのに利用している enebular は、以下の公式ドキュメントにも書かれているように、IoT製品・サービスを作るのに活用できたりもする開発・運用サービスです。

●イントロダクション · enebular
　https://docs.enebular.com/ja/

自分は、オンラインでビジュアルプログラミングの Node-RED を使える環境として利用する、という使い方が主です。無料枠もあるので、プライベートで関わっている技術コミュニティ界隈でのハンズオンに利用したこともあります（その他、個人のプロトタイピングなどにも利用しています）。

●Node-RED（enebular）でダッシュボード・リアルタイム通信を利用する【ハンズオン資料】 - Qiita
　https://qiita.com/youtoy/items/61e4a6c9061d68f09e3b

### LINE通知（LINE Notify）
今回の LINE通知で用いている[LINE Notify](https://notify-bot.line.me/ja/)は、Webサービスからの通知を LINEアプリで受信するのに、実装を簡単にしてくれる仕組みです。

これを使うと、LINE が提供する公式アカウント「LINE Notify」から、LINEアプリに通知が届きます。また、特定のグループに LINE Notifyアカウントを追加して、任意のグループへ通知を送る使い方もできます。

詳細は公式の APIドキュメントをご覧ください ⇒　https://notify-bot.line.me/doc/ja/

## Webhookを受けとるための実装について
ここから、実装について解説していきます。
まずは、Zoom からの Webhook を enebular で受けとれるようにするための実装を進めます。

### Zoom Webhook の利用設定（途中まで）
Zoom Webhook の使い方については、ありがたいことに以下の記事で丁寧に説明されています。
手順は以下の記事をベースにします（※ https://marketplace.zoom.us/ へサインインしたり、その後に進める手順について）。

●はじめての Zoom Webhook - Qiita
　https://qiita.com/yosuke-sawamura/items/a45453f1492a43211b96

ここでは、上記の手順と異なる差分のみを記載することにします。

#### イベントタイプの選択
今回は、Webhook で受けとりたい Zoom のイベントがミーティングの開始なので、「Event types」の部分では、以下の設定を行います。
![イベントタイプの選択](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/937231a8-fcf1-029c-14a9-5e600107271f.png)

また、Zoom側の設定の手順中に Webhook の URL を設定するところがありますが、そこで指定する URL の準備ができてないため、enebular の設定を進めます。

### enebular で Webhook を受けるための対応
ここで、Zoom からの Webhook を受ける部分を enebular で用意します。

enebular の使い方やセットアップの詳細まで書くと内容が長くなりすぎるため、ここでは割愛します。
それでは enebular でフローを作成する、以下の画面の説明から始めていきます。
![enebular でフローを作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c27a8e84-f0aa-0958-b577-0845a0c2e088.png)

#### 単純にWebhook を受けるだけの設定（※ 実装不足で失敗）
まずは、Webhook を受けるだけのフローを準備してみます。
![最初の Webhook を受けるためのフロー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/af8d84bf-5598-fa8e-e50f-714e509ca7ff.png)

「http in」のノードについては、メソッドの設定を「POST」に変更します。
また、URL の最後につける文字列として「zoom」を設定しました。
![http inノードの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/879cbd8a-93ed-99b1-6251-82cbbd96316a.png)

ここまで作れたら、enebuar側で画面右上の「保存」ボタンを押します。
そして、画面右上の「i」の文字のアイコン（「保存」ボタンの左にあるもの）にマウスカーソルを合わせて、そこで表示される URL を確認・メモします。
![URL の確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/8c9ce139-f543-d7e7-b7d6-f0a9f33ce8f5.png)
この URL の最後に、先ほど http in ノードの設定で指定した「zoom」という文字列をつけたものが、Zoom側で設定すべき URL になります。

それでは、ひとまず　Zoom側で Webhook の URL を指定して、バリデーションを試してみます。
試してみたところ、バリデーションに失敗しました。
![URL のバリデーション](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/eb9ea746-92dc-4abc-2162-99f4bc581693.png)

enebular側のデバッグ出力を確認してみます。
そうすると、HTTPリクエストは受けられており、以下のような内容を受信できていました。
![enebular](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9dd41290-9bc6-e847-afce-32c042233729.png)

この URL のバリデーションについて、公式のフォーラムやドキュメントで対応方法などを見てみます。
以下の内容が関係していそうです。

- [Event notification endpoint URL - URL validation failed. Try again later Webhook Only App - API and Webhooks / Webinars - Zoom Developer Forum](https://devforum.zoom.us/t/event-notification-endpoint-url-url-validation-failed-try-again-later-webhook-only-app/74014)
- [Using webhooks： Validate your webhook endpoint](https://marketplace.zoom.us/docs/api-reference/webhook-reference/#validate-your-webhook-endpoint)

「Validate your webhook endpoint」と書かれた部分で、「2022/10/23 以降に作成された Webhook は、バリデーションが必須」と書かれており、この対応が不足している状況のようです。
![Validate your webhook endpoint](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9af585c1-540e-c570-5e92-59222a21bea2.png)

公式のサンプルにあるような、以下のレスポンスを返す必要があるようです。
![公式サンプル：レスポンスの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/20189327-e1e3-c2ac-24cb-c3b2ffb464fc.png)

上記の「encryptedToken」を求める実装例として、Node.js を使ったものが掲載されていました。
![encryptedTokenを求めるプログラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/b92ac12a-723c-3ee0-88e9-14bc5ff7bb91.png)

```javascript
const crypto = require('crypto')

// Webhook request event type is a challenge-response check
if(request.body.event === 'endpoint.url_validation') {
  const hashForValidate = crypto.createHmac('sha256', ZOOM_WEBHOOK_SECRET_TOKEN).update(request.body.payload.plainToken).digest('hex')

  response.status(200)
  response.json({
    "plainToken": request.body.payload.plainToken,
    "encryptedToken": hashForValidate
  })
}
```

#### Webhook で受けとった内容を処理してバリデーションに対応する
上記の Webhook のバリデーションが通るように対応していきます。
enebular（Node-RED）上で HMAC SHA-256 を利用する方法について調べたところ、以下のノードを活用すれば良さそうでした。

●node-red-contrib-crypto-js (node) - Node-RED
　https://flows.nodered.org/node/node-red-contrib-crypto-js

ノードを追加して、対応を進めます。
右上のメニュー内の「パレットの管理」から「ノードを追加」を選び、そこで「node-red-contrib-crypto-js」のノードを追加する操作を行います。その結果、以下が使えるようになりました。
![node-red-contrib-crypto-jsの追加で利用可能になったノード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/29e1c748-71d9-a58a-58a0-d874bde00c82.png)

そして、Node.js のプログラムと同じ処理ができるように、以下のフローを組み立てます。
![Webhook のバリデーションに対応したフロー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/732fc9f6-d2c3-82c9-f5ed-10e961e2a1ea.png)

追加したのは以下の部分になります。
![追加したノード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/486a26dc-cd65-a29b-b946-942020709cb0.png)

それぞれ、3つの設定内容を処理順で掲載します。
![changeノードの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/bd8dcc65-b568-ff29-8f42-0979b6b3becb.png)

![hmacノードの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/79640f8f-3cfe-ad19-b761-5fe3c6d4923f.png)
※ Secret　Key は Zoom のシークレットキーで、本来は環境変数経由で利用するのが良いですが、ここではとりあえずのお試しなので直書きで

![templateノードの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0169cf0a-c5b4-2629-4c2b-fd17fced248e.png)

これで無事にバリデーションが OK になり、Zoom側の設定も完了しました。
![Zoom側の設定完了](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/58e46a49-6c12-f199-8aaa-8fd05f76947e.png)

#### Zoomミーティングを開いて動作確認
Zoomミーティングを開いて動作確認もしてみます。

enebular の http in ノードの出力をデバッグにつなぎ、その後に Zoomミーティング を開いて、Zoom から受けとった Webhook を直接出力できるようにした上で、Zoomミーティングを開きました。
その結果、以下のようにミーティング開始時のイベントを受けとることができていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/611b21d6-85d2-4722-9c0d-8e5966738941.png)

## Webhookを受けとった後の実装
無事に、Zoom からの Webhook を enebular で受けとることができたので、enebular から別サービスなどへつなぐ実装を追加します。

### LINE通知を受けとる
別サービスへつなぐ話の 1つは、LINEアプリ連携です。
LINE Notify というサービスを利用し、手軽な LINE アプリへの通知を行います。

#### LINE Notify の準備
LINE Notify を利用するための準備を書いていきます。
ここで、LINE Notify 利用前の LINEアプリの準備の詳細まで解説すると、内容が長くなりすぎるため、その部分の説明は公式の説明・Web上の記事に譲ることとし、ここでは割愛します。

それでは、「LINE Notify のサイト」 ⇒ https://notify-bot.line.me/ja/ へアクセスします。
![LINE Notify](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6d00bdb4-0023-7444-a9f8-c2e798f167f3.png)

上記は、LINEアプリのアカウントでログインした状態で、その状態だと画面右上のメニューを開けるようになっています。「マイページ」を選んでください。
![LINE Notifyのメニューを開く](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/75ec0f7e-5d33-a3db-a00c-a11d45c6d9f4.png)

その後のページで、下のほうに「トークンを発行する」というボタンがあるので、これを押します。
![トークンの発行操作](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ed004aa4-b5eb-1530-4f24-4ef7d0395aa1.png)

トークンの名称は分かりやすい内容に設定して、LINE通知を送るトークルームを選択し、「発行する」ボタンを押します。
![トークンの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d730a0e2-1bbe-3e39-25f9-07f6834e399d.png)

そして、以下の画面でトークンが表示されるのでメモしておきます。
1つ注意点があり、画面上に書いてあるとおりトークンは 2度と表示されないため、ここでメモしたものは自分で保存しておいてください。
![トークンが表示された画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/230c39b3-9f5a-3054-0651-2c940ca335a6.png)

#### enebular で LINE Notify を使う
enebular で LINE Notify を使う設定を行います。
それについて、過去に書いた以下の記事で説明を書いたことがあるので、それを利用します。

●Node-RED で functionノードや専用ノードを使わず LINE Notify を利用する（changeノードを利用、Sticker送信も試す） - Qiita
　https://qiita.com/youtoy/items/ad4651b6438040a353f6

LINE通知を追加実装したフローは、以下の通りです。
![LINE通知を追加実装したフロー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9544787e-69e4-f62d-fa75-a9d9c807b172.png)

フローで追加した部分について、少し補足します。
デバッグ用のノードを除くと、追加した部分は以下の赤矢印で示した通りです。
![追加した部分](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/76282397-4fe9-b2b5-da03-1e4842cac242.png)

「switch」ノードは、Zoom Webhook で使ったバリデーションの処理と、LIEN通知の処理を分けるものです。
![「switch」ノードの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/b0abbae8-10b4-b164-469a-c2bb8ba9014a.png)

`msg.payload.event` の値が、 `endpoint.url_validation` か `meeting.started` になるか（＋ それ以外か）で分岐をさせるものです。
`endpoint.url_validation` のイベントの場合は、先ほど作成した流れにつなげます。

そして、 `meeting.started` の場合は、LINE通知のための処理で、実装の話は以下に続きます。

分岐直後の「change」ノードでは、過去の記事にも書いていた、LINE通知用（LINE Notify用）の HTTPリクエストのヘッダに含める情報を設定します。
![分岐直後の「change」ノード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d6632b11-cd1d-85d4-fc3e-fa4ab924ae7a.png)
LINE Notify のトークンは、本来は、環境変数経由などにするのが良いですが、ひとまずお試しでの手順簡単化のために、直書きにしています。

分岐後 2つ目の「change」ノードでは、LINE通知の本文を作成しています。
![分岐後 2つ目の「change」ノード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/8489d04c-24fe-72a2-0b4d-3ddac439f99d.png)

`msg.payload.message` が本文になるので、そこに、「Zoom会議名」と「会議が開始された旨のテキスト」を連結させて設定します。
Zoom から受けとった Webhook のトピック、enebular（Node-RED）の上では `msg.payload.payload.object.topic` に会議名が入っています。そこで、「JSONata」を使って `msg.payload.payload.object.topic & "の会議が開始されました"` という内容で、payload 中の文字列と自分が用意した文字列の連結を行います。

最後に、HTTPリクエストの処理の部分です。
![HTTPリクエストの処理](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/4ffb076a-b51f-1297-2a43-73b510ca1bf5.png)

ここは、過去の記事でも書いたように、以下を設定します。

- メソッド： POST
- URL： https://notify-api.line.me/api/notify

これで、フローを保存しましょう。

#### 動作確認
最後に動作確認です。
Zoom Webhook を有効にしたアカウントで、Zoom会議を作成して、その会議を開始させましょう。

会議開始は、手軽に開催できる、以下のメニューから実施してみました。
![会議開始](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d8e3ce23-495e-4ca2-4bc1-cf5322f05564.png)

上記を用いた場合、会議名は、アカウントの名前を含む「Yosuke ToyotaのZoomミーティング」になります。
会議を開始した時、スマホの LINEアプリの通知などを確認すると、以下のように会議開始のお知らせ通知が来ていました。
![LINEアプリへの通知](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ec0bc231-d1ee-d87d-e0cb-7dffce920baf.png)

これで、完了です！

## 他の応用
今回、enebular で Zoom Webhook を受けた際に、その出力は LINEアプリへの通知を使いました。
その他にも、enebular では MQTT や 他サービスとの連携を行えるノードも活用できます。

例えば、自分が enebular で実装したことがある例では、MQTT を使った M5Stack社のデバイスの連携があります。具体的には、デバイス内蔵のスピーカーから音を鳴らしたり、内蔵の電子ペーパーの画面を書きかえたり、外付けの フルカラーLEDテープを光らせたりなどです(※ 以下の動画のとおり)。

●【LINE で IoT】リッチメニューの操作でデバイス連携 ＋ デバイスのボタン押下で LINE通知 - YouTube
　https://www.youtube.com/watch?v=7kbdeFPKUoc

<iframe width="560" height="315" src="https://www.youtube.com/embed/7kbdeFPKUoc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

LINEアプリへの通知だけでなく、身近に置いた小型デバイスへ通知して、文字・音・光など色々な方法でお知らせするというのも、面白いかもしれません。

●ビジュアルプログラミングのみで作る LINE ＋ IoT の仕組み / LINE DC Monthly LT #1 - Speaker Deck
　https://speakerdeck.com/you/line-dc-monthly-lt-number-1
<script async class="speakerdeck-embed" data-id="753f209cf1c84dcf83b38465edc888a3" data-ratio="1.77613320999075" src="//speakerdeck.com/assets/embed.js"></script>

## 関連URL
- [crypto モジュール - IBM Documentation](https://www.ibm.com/docs/ja/datapower-gateway/7.6?topic=methods-crypto-module#crypto.createHmac)
- [Node-red+JSONataでfunction地獄からの卒業](https://www.slideshare.net/kazuhiroharada2/node-redjso-natafunction)
- [JSONata の言語ガイドを訳してみた - Qiita](https://qiita.com/yamachan360/items/91805334890d18ef6e23)