現行の GAS では Zoom API の webhook を有効化できない


# TL;DR
GAS では、以下の要因で Zoom API の webhook を有効化できない。
+ GAS は Zoom API の webhook 有効化に必要なレスポンスを、**ステータスコード 302 経由でリダイレクトされた別の エンドポイント**から返す。
+ しかし、Zoom 開発者ページの 有効化チェック処理では、**インプットボックスに指定したエンドポイント** からのレスポンスしか受け付けない。


---

Zoom APIとLINE Messaging API / Notify APIなどを使って会議の開始をアラートしたりスケジューラと組み合わせたりするのが一時期流行してました。

これを無料で敷居の低いGASで利用できないか、というのも１つ。

しかし、結論として、現状の Google Apps Script でデプロイした 野良API で、現行の Zoom webhook を有効化することは不可能のようです。

これは以下の理由によります。
① Zoom webhook を有効化するには、Zoomの開発者ページで、webhook URLのテスト用の "Validateボタン君"を突破する必要がある。
（ここでの webhook URLとは、GAS でデプロイして発行した URL のこと）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/1620ba14-8f1c-d79d-811c-c0f75de04b31.png)


② 一方、GASで作った野良API は、クライアントからの、デプロイURL への Post リクエストに対するレスポンスを返す際、デプロイURLからではなく、**ステータスコード 302 によるリダイレクトを経由し別のエンドポイントから返す。**

具体的には、
`https://script.google.com/macros/s/++++++++/exec` から
`https://script.googleusercontent.com/macros/echo?xxxxxx` にリダイレクトされ、そこからレスポンスが返ってくる。

---

下図は、POSTMAN を使い、[Zoom のリクエストをエミュレートしたもの]を、 GAS で発行したデプロイ URL に対して POST したときのコンソール画面。

`https://script.google.com/macros/s/++++++++/exec` に対するリクエストが、ステータスコード 302(Found) でリダイレクトされ、別の URL（`https://script.googleusercontent.com/macros/echo?xxxxxx`）からレスポンスが返ってきていることがわかる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/a9381be5-ede6-7110-237d-84c603b67606.png)

---


③ しかし、Validateボタン君は、[ページで指定したwebhook URLからのレスポンスしか正当なものと認めない](https://marketplace.zoom.us/docs/api-reference/webhook-reference/#4-respond-with-the-response-json-object:~:text=Respond%20with%20the%20response%20JSON%20within%203%20seconds%20with%20a%20200%20or%20204%20HTTP%20response%20code) 。
つまり、GAS 野良 API からの、（302 でリダイレクトされた）**別のエンドポイントからのレスポンスを、正当なレスポンスと認めない。（たとえハッシュが正しくても。）**

 ⇒ 現行のGoogle Apps Script の野良APIは、Postリクエストを受けたときそのレスポンスは 302 を経由します。これは設定でどうにかできるものではなさそう。

なので、Zoom API の webhook を使いたい場合は、GAS ではなく Node とか別の手段を使いましょう、ということになるでしょうか。


※この点、LINE Mesaging APIの開発者ページのwebhookの「検証」ボタンでは、GAS でデプロイしたURL でも問題なく通ります。（そもそもLINE Mesaging API の開発者ページにおける「検証」は、そのエンドポイントにアクセスできるかしか調べておらず Validation を行っていないので比較対象にならないかもしれませんが。）

---

# ソースコード（参考）
ちなみに、ZOOM API からの Validation リクエストに対して 正しい内容を吐くGASのコードは下記になります。（ドキュメントをきちんと読めば簡単に作れます）
**ただし下記は 上記に書いた GAS の仕様と Zoom 開発者ページの仕様により、検証が通りません。**
（もしValidate ボタン君がもうちょっと柔軟な対応をしてくれたら・・・）
```js
const byteToHex = (bytearray) => bytearray.reduce((str, chr) => {
  chr = (chr < 0 ? chr + 256 : chr).toString(16);
  return str + (chr.length == 1 ? '0' : '') + chr;
}, '');

function doGet(e) {
  const output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(JSON.stringify({"message":"null"}));
  return output;  
}

function doPost(e) {
  const res = JSON.parse(e.postData.contents);
  const hashForValidate =  byteToHex(
    Utilities.computeHmacSignature(
      Utilities.MacAlgorithm.HMAC_SHA_256, 
      res.payload.plainToken, 
      "***SECRET_KEY***"
    )
  );
  const message = {
    plainToken: res.payload.plainToken,
    encryptedToken: hashForValidate
  };
  const output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(JSON.stringify(message));
  return output;
}
```