はじめに
以前の記事では、Zoom Team Chat の Incoming Webhook を中心に紹介してきました。
今回は同じ Webhook でも Outgoing Webhook つまり、Zoom から外部サービスへの Webhook 通知を扱ってみたいと思います。
下準備
利用するのは Webhook Only と呼ばれる機能で、以前こちらの記事でも紹介したことがあります。
そもそも Webhook とは何か、や基本的な仕様、実装準備などについては上記の記事を参照ください。
特に Challenge-response check (CRC) の対応は少し組み込みに時間がかかるかもしれませんが、こちらの記事を参照していただければ、割とすんなり実装できると思います。
この記事に沿って、サーバが指定ポートで listen した状態で、2.2にある追記部分くらいまで完了してください。(今回は録画データを扱わないので、録画が完了したイベント「recording.completed」を受けての処理周りは含めなくてOKです)
Slack での Incoming Webhook の準備
これもあえてこの記事で説明するほどではありませんが、Manage Apps から Incoming Webhook を検索し、インストール。
新規の Incoming Webhook を追加して、 Webhook URL を取得します。こちらの記事なんかはわかりやすいですね。
Webhook Only アプリの追加
今回のアプリは、 Zoom Phone の不在着信や留守電を受け取ったら Slack に通知するという処理なので、Webhook Only アプリの画面の Feature セクション内、Event Subscription で追加する Events (Add Events から追加します)は、画像のように Callee missed a phone call と Voicemail is received の2つを選択します。
Web サーバ側の実装について
ここからは Node.js 環境での実装になります。(事前に HTTPS 通信が可能な Web サーバの準備が必要です)
以下は準備です。
require('dotenv').config();
const https = require('https');
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
const port = 3000;
また、bodyParser を明示します。(expressのバージョンによっては、body-parserはdeprecatedになっているようなので、expressだけで良いかもしれません)
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
早速POSTされた時の処理ですが、こちらは上述の記事にあるのでほぼ再掲となります。
app.post('/', (req, res) => {
console.log(req.body);
console.log("POST");
var response;
console.log(req.headers);
console.log(req.body);
response = { status: 200 };
res.status(response.status);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
ひとまずこれで、このエンドポイントに POST アクセスがあった際に HTTP 200 を返す形になるかと思います。とりあえずなので、どんなリクエストが来ても OK! OK! と返す変なヤツになってますね。
Secret Token を基に識別
自分が設定した Webhook からの POST イベントのみを識別するため、Webhook Only アプリ作成時に生成された Secret Token を基に識別する仕組みを作ります。あらかじめアプリと同じディレクトリに .env
ファイルを配置しておきます。
ZOOM_WEBHOOK_SECRET_TOKEN="{YOURSECRETTOKEN}"
このような内容を記載して保存しつつアプリ側から呼び出し、Signature を生成した上でWebhook のヘッダーに含まれる x-zm-signature
を比較して識別します。さらに、Zoomからの Challenge-response check (CRC) の対応として、event が endpoint.url_validation の場合に、指定されたレスポンスを返却するよう処理を追加します。
if (req.headers['x-zm-signature'] === signature) {
console.log("SecretTokenVerification : true");
if(req.body.event === 'endpoint.url_validation'){
console.log('endpoint.url_validation');
const hashForValidate = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(req.body.payload.plainToken).digest('hex');
response = {
message: {
"plainToken": req.body.payload.plainToken,
"encryptedToken": hashForValidate
},
status: 200
};
console.log(response.message);
res.status(response.status);
res.json(response.message);
}else{
WriteToLog("SecretTokenVerification : false");
response = { message: 'Zoom Webhook Demo Response', status: 200 };
res.status(response.status);
res.json(response);
}
};
ここまでできたら、今度は event ごとの処理の追加だけですね!switch case を使って一気に追加しましょう。
イベントごとの処理の追加
今回は、 Callee missed a phone call と Voicemail is received の2つですので、これらの Webhook の Body をみながら、パースしてデータを取得していきます。
if (req.headers['x-zm-signature'] === signature) {
console.log("SecretTokenVerification : true");
let slackBody = "";
switch (req.body.event) {
case 'endpoint.url_validation':
console.log('endpoint.url_validation');
const hashForValidate = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(req.body.payload.plainToken).digest('hex');
response = {
message: {
"plainToken": req.body.payload.plainToken,
"encryptedToken": hashForValidate
},
status: 200
};
console.log(response.message);
res.status(response.status);
res.json(response.message);
break;
case 'phone.callee_missed':
console.log('Webhook Notification: '+'phone.callee_missed');
response = {status:200};
res.status(response.status);
const callerPhoneNumber = req.body.payload.object.caller.phone_number;
const callType = req.body.payload.object.callee.extension_type;
const calleePhoneNumber = req.body.payload.object.callee.phone_number;
const call_end_time = req.body.payload.object.call_end_time;
slackBody = ":telephone_receiver: Missed call notification. \nCaller: "+callerPhoneNumber+"\nCallee: "+calleePhoneNumber+"\nType :"+callType+"\nTime: "+call_end_time;
console.log(slackBody);
sendToSlack(slackBody);
break;
case 'phone.voicemail_received':
console.log('Webhook Notification: '+'phone.voicemail_received');
response = {status:200};
res.status(response.status);
const vcaller_number = req.body.payload.object.caller_number;
const vcaller_name = req.body.payload.object.caller_name;
const vdate_time = req.body.payload.object.date_time;
slackBody = ":telephone_receiver: Voice mail notification. \nCaller: "+vcaller_number+"\nName: "+vcaller_name+"\nTime: "+vdate_time;
console.log(slackBody);
sendToSlack(slackBody);
break;
default:
break;
}
}else{
console.log("SecretTokenVerification : false");
response = { message: 'Zoom Webhook Demo Response', status: 200 };
res.status(response.status);
res.json(response);
}
Body の詳細はリファレンスに記載があるので、そちらを参照していただきつつ、必要な情報を取り出してください。
Slack に通知
すでに上のコードにも含まれていますが、 sendToSlack(slackBody)
の部分を書いていきます。
function sendToSlack(slackBody) {
var data = JSON.stringify({"username":"Zoom Notification","text": slackBody,"icon_emoji":":zoom:"});
//icon_emoji :zoom: は事前にご自身で追加してくださいね!
var options = {
hostname: 'hooks.slack.com',
port: 443,
path: '/services/XXXXXXXXXXXXXX',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
var req = https.request(options, (res) =>{
if(res.statusCode===200){
console.log("OK:"+res.statusCode);
}else{
console.log("Status Error:"+res.statusCode);
}
});
req.on('error',(e)=>{
console.error(e);
});
req.write(data);
req.end();
}
以上でコードとしては終了です。( app.listen
が最後にきます)
ここまでできたら、Zoom Phoneで実際に不在着信と留守電を試してみましょう。このような形で、着信履歴や留守電の通知がきます。
サンプル・ドキュメント類について
概要 (Using Webhook):
各種イベントの詳細 (オフィシャル・ドキュメント):