WhatsApp Flowsは、WhatsApp上だけでアクションが完結するインタラクティブなメッセージを構築できる機能です。Flowsでは、ユーザーのアクションを受け付ける画面を作成できます。例えば、リードの獲得やレビューの収集のためのシンプルな入力フォームなどです。加えて、複数の画面を使って予約の日時を設定する複雑なFlowsをデザインすることも可能です。
このガイドでは、WhatsApp Flowsで予約を受け付けるためのNode.jsアプリの構築方法について説明します。WhatsApp BusinessプラットフォームでFlowを作成し、続いて、そのFlowのレスポンスを受け取って予約を受け付けるWebhookを設定します。
このチュートリアルを実行するには以下のものが必要です。
WhatsApp Flowを作成するには、Flows Builder (WhatsAppマネージャからアクセス可能)を使う方法とFlows APIを使う方法の2通りがありますが、このチュートリアルではFlows Builderを使用します。
WhatsAppマネージャダッシュボードの左側のメニューで[アカウントツール]を選択します。次に、[Flows]をクリックします。
右上の[Flowを作成]をクリックします。
表示されるダイアログボックスに、予約Flowの詳細を入力します。
[送信]をクリックしてFlowを作成します。
Builder UIの右側にFlowのプレビューが表示されます。予約画面では、予約の詳細(場所、日付など)を選択してもらいます。ユーザー情報画面では、ユーザーの情報を入力してもらいます。予約確認画面には予約の概要が表示されます。最後の画面には会社の規約が表示されます。
編集している間は、Flowは下書き状態のままです。この段階では、社内でのテスト目的の共有しかできません。広く共有するにはFlowを公開する必要があります。ただし、公開した後はFlowを編集することができません。この予約FlowにはまだエンドポイントURLを追加する必要があるので、ここでは下書きのままにしておき、次のエンドポイント設定ステップに進みましょう。
WhatsApp Flowsには、外部のエンドポイントに接続できる機能があります。このエンドポイントからFlowに動的データを提供し、ルーティングをコントロールすることができます。ユーザーから送信されたレスポンスをFlowから受け取ることも可能です。
テストの目的上、この記事ではGlitchを使用してエンドポイントをホストします。Glitchの使用はまったくの任意であり、Flowsの使用に必須というわけでありません。GitHubからエンドポイントコードをクローンし、任意の環境で実行してかまいません。
Glitchのエンドポイントコードにアクセスし、それをリミックスして固有のドメインを得ます。リミックスするには、ページ上部の[Remix]をクリックします。Glitchページの右側の入力要素に、固有のドメインがプレースホルダーとして表示されます。
先に進む前にコードの中身を見ていきましょう。src
ディレクトリには、encryption.js
、flow.js
、keyGenerator.js
、server.js
、という4つのJavaScriptファイルがあります。エントリーファイルはserver.js
です。まずはこのファイルから見ていきます。
server.js
ファイルは、express.json
ミドルウェアを使用して受信JSONリクエストを解析するために、まずExpressアプリケーションを設定します。次に、エンドポイントに必要な環境変数を読み込みます。
const { APP_SECRET, PRIVATE_KEY, PASSPHRASE, PORT = "3000" } = process.env;
署名認証にはAPP_SECRET
を使用します。これにより、メッセージの送信元がWhatsAppであり、よって安全に処理できるかどうかを確認できます。これを.env
ファイルに追加します。
APP_SECRET
にアクセスするには、Meta for Developersのアプリのダッシュボードを開きます。左側のナビゲーションパネルにある[アプリの設定]で、[ベーシック]を選択します。[app secret]の下で[表示]をクリックし、秘密鍵をコピーします。次に、Glitchに戻って.env
ファイルを開き、コピーした秘密鍵の値でAPP_SECRET
という変数を作成します。
PRIVATE_KEY
は、受信したメッセージを復号するためのものです。PASSPHRASE
は、非公開鍵の検証に使用します。非公開鍵のほかに、それに対応する公開鍵も必要です。これは後ほどアップロードします。本番用アカウントの非公開鍵は絶対に使用しないでください。Glitchでのテスト用に仮の非公開鍵を作成し、その後、自社のインフラ内で本番用の鍵に差し替えてください。
Glitchのターミナルで以下のコマンドを実行して公開鍵と非公開鍵のペアを生成します。<your-passphrase>
には、自身で決めたパスフレーズを使用してください。Glitchのターミナルにアクセスするには、ページ下部の[TERMINAL]タブをクリックします。
node src/keyGenerator.js <your-passphrase>
パスフレーズと非公開鍵をコピーし、.env
ファイルにペーストします。左側のサイドバーで拡張子が.envのファイルをクリックしてから、上部の[✏️ Plain text]をクリックします。鍵のフォーマットが崩れてしまうため、UIで直接編集しないでください。
環境変数を設定したら、生成した公開鍵をコピーし、グラフAPIで公開鍵をアップロードします。
server.js
ファイルには、以下の各ステップを実行するPOST
エンドポイントも含まれています。
if (!PRIVATE_KEY) { throw new Error('非公開鍵が空です。環境変数"PRIVATE_KEY"を確認してください。'); }
isRequestSignatureValid
関数を使ってリクエストの署名を検証します: if(!isRequestSignatureValid(req)) { // リクエストの署名が一致しない場合はステータスコード432を返します。// 返すエラーコードについて、詳しくはhttps://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codesをご覧ください。 return res.status(432).send(); }
encryption.js
ファイルにあるdecryptRequest
関数を使って受信メッセージを復号します: let decryptedRequest = null; try { decryptedRequest = decryptRequest(req.body, PRIVATE_KEY, PASSPHRASE); } catch (err) { console.error(err); if (err instanceof FlowEndpointException) { return res.status(err.statusCode).send(); } return res.status(500).send(); } const { aesKeyBuffer, initialVectorBuffer, decryptedBody } = decryptedRequest; console.log("💬 復号されたリクエスト:", decryptedBody);
getNextScreen
関数については後ほど詳しく見ていきます。const screenResponse = await getNextScreen(decryptedBody);
console.log("👉 暗号化するレスポンス:", screenResponse);
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));
このファイルには、交換されるメッセージのセキュリティ確保を目的とした暗号化と復号のロジックが含まれます。このチュートリアルでは、このファイルの扱いについては取り上げません。
このファイルは、前述したように非公開鍵と公開鍵の生成に使用します。encryption.js
ファイルと同様、keyGenerator.js
ファイルについてもここでは詳しくは取り上げません。
Flowの処理ロジックはこのファイルに格納されています。SCREEN_RESPONSES
という名前が割り当てられたオブジェクトから始まります。このオブジェクトには、画面IDとそれに対応する情報(データ交換で使用されるプリセットデータなど)が含まれます。このオブジェクトは、Flow Builderの[...] >[エンドポイント] > [スニペット] > [返信]で生成されます。この同じオブジェクトには、別のID「SUCCESS
」もあります。これは、Flowが正常に完了したときにクライアントデバイスに返されます。これによってFlowが閉じます。
getNextScreen
関数には、ユーザーに表示するFlowデータをエンドポイントに伝えるロジックが含まれます。この関数は、はじめに、復号されたメッセージから必要なデータを抽出します。
const { screen, data, version, action, flow_token } = decryptedBody;
WhatsApp Flowsのエンドポイントは通常、3つのリクエストを受信します。
data_exchange
アクションで示されるdata_exchangeリクエストdata.error
要素で示されるエラー通知リクエストping
アクションで示されるヘルスチェックリクエストこれらの詳細についてはエンドポイントのドキュメントをご覧ください。
この関数は、以下のスニペットに示すとおり、if
文を使ってヘルスチェックとエラー通知を処理し、適宜レスポンスします。
// ヘルスチェックリクエストを処理 if (action === "ping") { return { version, data: { status: "active", }, }; } // エラー通知を処理 if (data?.error) { console.warn("クライアントエラーを受信しました:", data); return { version, data: { acknowledged: true, }, }; }
ユーザーがFlowのCTAボタンをクリックすると、INIT
アクションが実行されます。このアクションは、データの入った予約画面を返します。ユーザーが確実にすべてのフィールドに入力するように、場所、日付、時刻のドロップダウンの無効化も行います。
例えば、日付ドロップダウンは場所ドロップダウンが入力されたときしか有効になりません。フィールドの有効化と無効化は、data_exchange
リクエストを受信したときに処理されます。
// Flowを開くときに最初のリクエストを処理し、APPOINTMENT画面を表示する if (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // 以下のフィールドは最初は無効化されています。各フィールドは、その1つ前のフィールドが選択されたときに有効になります。 is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }
data_exchange
アクションでは、画面IDに基づいて返すべきデータを判断するためにswitch case文を使用しています。画面IDがAPPOINTMENT
の場合、各ドロップダウンフィールドは、その1つ前のドロップダウンが選択されたときしか有効になりません。
// 各フィールドは、その1つ前のフィールドが選択されたときのみ有効になる is_location_enabled: Boolean(data.department), is_date_enabled: Boolean(data.department) && Boolean(data.location), is_time_enabled: Boolean(data.department) && Boolean(data.location) && Boolean(data.date)
DETAILS画面では、データオブジェクトプロパティ(場所や部署など)のタイトルはSCREEN_RESPONSES.APPOINTMENT.data
オブジェクトから抽出されます。このコードは有効な一致があることを前提としているため、一致するオブジェクトが見つからなかった場合はエラーが起きる可能性があることに注意してください。
場所オブジェクトを例に見てみましょう。特定の場所オブジェクトの選択は、配列内のオブジェクトのid
プロパティとdata.location
の値を照らし合わせることで判断されます。
const departmentName = SCREEN_RESPONSES.APPOINTMENT.data.department.find( (dept) => dept.id === data.department ).title; const locationName = SCREEN_RESPONSES.APPOINTMENT.data.location.find( (loc) => loc.id === data.location ).title; const dateName = SCREEN_RESPONSES.APPOINTMENT.data.date.find( (date) => date.id === data.date
).title;
続いて、値が連結されてレスポンスで返され、SUMMARY画面が生成されます。
const appointment = `${departmentName} at ${locationName} ${dateName} at ${data.time}`; const details = `Name: ${data.name} Email: ${data.email} Phone: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // 次のステップで送信してもらうために、クライアントから送信されたのと同じフィールドを返す ...data, }, };
SUMMARY画面でクライアントが送信を選択すると、完了レスポンスがクライアントデバイスに送信されてFlowが完了とマークされます。flow_token
は、ユーザーにFlowを送信するときに設定できる固有の識別子です。
// 完了レスポンスを送信し、Flowを完了して閉じます send success response to complete and close the flow return { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };
TERMS画面には交換するデータがないため、エンドポイントはこの画面を処理しません。
Glitchページの右上にある縦3点ドットメニューをクリックし、[Copy Link]を選択するとURLをコピーできます。リンクのコピーは右上の[Share]をクリックすることでも行えます。
Flowエディターに移動します。エディターの上部に表示されるブラウンのバナーで[設定]をクリックします。
ポップアップが表示されます。ここで、エンドポイントURI、ビジネスの電話番号、Meta for Developersのアプリを設定できます。必要な設定を済ませたら、次はヘルスチェックです。まず、インタラクティブプレビューを実行し、インタラクティブプレビュー設定の[最初の画面のデータをリクエスト]で[データをリクエスト]を選択します。これは、最初の画面のデータを取得するリクエストをエンドポイントに送信して、エンドポイントがアクティブであること、およびヘルスチェックが実行済みであることを検証するための設定です。
続いて、横3点ドットメニュー([...])をクリックし、[公開]を選択してFlowを公開します。action === "ping"
によってエンドポイントにヘルスチェックリクエストが送信され、エンドポイントが設定されていることの検証が行われた後、公開されます。
設定が完了したら、もう一度WhatsApp Builder UIのインタラクティブプレビューに切り替えてFlowをテストします。表示されるポップアップで、電話番号を選択し、[最初の画面のデータをリクエスト]で[データをリクエスト]オプションを選択します。CTAボタンからFlowのテストを開始できるように、XアイコンをクリックしてFlowを閉じます。
[LOGS]タブをクリックしてGlitchのログを開きます。[Clear]をクリックしてログをクリアします。続いて、WhatsApp Builder UIのプレビューに戻ります。[Flowをプレビュー]をクリックします。次のようなものが表示されます。
次に、Glitchのログに戻ります。復号されたリクエストの下に、INIT
アクション、Flowのトークン、その他の情報が表示されます。部署ドロップダウンが選択されたときにユーザーのFlowに返す、暗号化対象のレスポンスもあります。
部署の選択に進みます。is_location_enabled
がtrue
に設定され、アクションがdata_exchange
に変わっています。
Flowのテストを継続し、Glitchのログでデータが変化することを確認します。ユーザーがモバイルデバイスからFlowを操作したときも、同じようなログが生成されます。
次のセクションでは、予約したユーザーに確認メッセージを送信するWebhookを作成します。
ユーザーがFlowを完了すると、Flowの完了を伝えるメッセージが設定済みのWebhookに送信されます。このWebhookから、チャットのメッセージでユーザーに予約が完了したことを伝えます。エンドポイントと同様、テストにはGlitchを使用します。コードへのアクセスとリミックスは、こちらから行えます。
Glitchの使用はまったくの任意であり、Flowsの使用に必須というわけでありません。GitHubからエンドポイントコードをクローンし、任意の環境で実行してかまいません。
環境変数を設定するには、Glitchで.env
ファイルを開きます。VERIFY_TOKEN
に任意の文字列を設定し、FLOW_ID
を自分のFlowのIDにし、GRAPH_API_TOKEN
にWhatsApp Businessアカウントのアクセストークンを設定します。アクセストークンは、Meta for Developersのアプリのダッシュボードで、左側のナビゲーションパネルの[WhatsApp]セクションで[APIの設定]をクリックすることにより取得できます。
表示されるページの[一時的なアクセストークン]で[コピー]をクリックします。キーを.env
ファイルにペーストします。
Meta for Developersの開発者アカウントに移動し、左側のナビゲーションパネルの[WhatsApp]で[設定]メニューをクリックします。
[Webhook]カードで[編集]をクリックします。表示されるダイアログの[コールバックURL]フィールドにコピーしたGlitch URLをペーストし、末尾に「/webhook
」を付けます。[トークンを認証]フィールドに、.env
ファイルのVERIFY_TOKEN
変数のトークンを追加します。完了したら[確認して保存]をクリックします。ダイアログが閉じ、メイン画面に戻ります。[管理]をクリックし、messagesフィールドを確認します。これでWebhookの準備が整いました。
コードには、POST /webhook
とGET /webhook
の2つのルートがあります。GET
は、Webhook認証リクエストを処理するルートです。提供されたトークンと定義済みの認証トークンを照合し、適切なステータスコードとチャレンジトークンを返します。
const verify_token = process.env.VERIFY_TOKEN; // Webhook認証リクエストのパラメーターを解析する let mode = req.query["hub.mode"]; let token = req.query["hub.verify_token"]; let challenge = req.query["hub.challenge"]; if (mode && token) { if (mode === "subscribe" && token === verify_token) { console.log("WEBHOOK_VERIFIED"); res.status(200).send(challenge); } else { res.sendStatus(403); } }
POST /webhook
は、受信Webhook通知を処理するルートです。Webhookリクエストは、さまざまなペイロードを持つ可能性があります。そのため、以下のコードは、リクエストフィールドが未定義の場合に備えてそのフィールドに安全にアクセスすることで、メッセージとビジネスの電話番号を読み取ります。
const message = req.body.entry?.[0]?.changes[0]?.value?.messages?.[0]; const business_phone_number_id =
req.body.entry?.[0].changes?.[0].value?.metadata?.phone_number_id;
その後、受信リクエストが、「appointment」(予約)の単語を含む"text"
タイプのメッセージかどうかを確認します。メッセージにその単語が含まれている場合はユーザーにFlowが送信されます。このFlowメッセージにはflow_action: "data_exchange
"
が含まれているため、Flowの起動時には、最初の画面とデータを取得するINIT
リクエストがエンドポイントに送信されます。
if ( message.type === "text" && // デモの都合上、ユーザーが「appointment」を含むメッセージを送信するたびにFlowメッセージが送信されるようになっています message.text.body.toLowerCase().includes("appointment") ) { // https://developers.facebook.com/docs/whatsapp/flows/gettingstarted/sendingaflow#interactive-message-parametersのドキュメントに従ってFlowを送信します await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, type: "interactive", interactive: { type: "flow", header: { type: "text", text: "こんにちは👋", }, body: { text: "空間をイメージチェンジしませんか?当社のエキスパートチームとの個別相談をご予約ください!", }, footer: { text: "予約に進むには以下のボタンをクリックしてください", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // flow_tokenをこのFlowメッセージの固有の識別子に差し替えてエンドポイントとWebhookでトラッキングします flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "予約する", flow_action: "data_exchange", }, }, }, }, }); } ...
受信メッセージのタイプが"text"
でない場合は、メッセージのタイプが"interactive
"
かどうかがチェックされます。インタラクティブなタイプ"nfm_reply"
は、受信メッセージがFlowレスポンスであることを示します。その後、「予約が完了しました」というメッセージがユーザーに返されます。
... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // 確認メッセージを送信します await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, text: { body: "予約が完了しました" }, }, }); } ...
その後、ユーザー側に青いチェックマークが表示されるように受信メッセージを既読にします。
... // 受信メッセージを既読にします await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", status: "read", message_id: message.id, }, }); ...
この例では、ビジネスの番号宛に「appointment」の単語を含むメッセージを送信したユーザーにFlowメッセージが送信されます。別のアクションの後にFlowを送信したり、Flowをメッセージテンプレートとして送信したりすることも可能です。
ユーザーには予約のCTAボタンを含むFlowメッセージが届き、ユーザーはそのボタンから情報を入力します。その後、Flowの操作を完了すると確認メッセージが届きます。
このガイドでは、シームレスに予約を受け付けるWhatsApp Flowの設定方法について説明しました。Flow Builder UIを使って、ユーザーから予約情報を取得するフォームの作成方法をお分かりいただけたと思います。
Flowsなら外部の予約用ウェブサイトにユーザーを飛ばす必要がないため、顧客体験が向上します。WhatsAppの中だけで予約が完了するのでプロセスがシンプルです。WhatsApp Flowsでできるのは予約だけではありません。サービスに対する顧客の声を集めたり、キャンペーンやメーリングリストに登録できるようにしたりするためにも応用できます。WhatsApp Flowsは、エンドポイントで外部のAPIや他のアプリと接続できる柔軟性も備えています。
Flow Builder UIならWhatsApp Flowsの作成も簡単です。もちろん、Flow APIを使ってFlowsをプログラムで作成することも可能です。詳しくはWhatsApp Flowsのドキュメントをご覧ください。