はじめに
お客様より「LINEを使ったサービスを展開したい」とご要望をいただき、LINE Bot(Messaging API)を利用した開発を行う機会がありました。
この記事では、その開発時に得た知識のうち、主にリッチメニューの動的な切り替えの必要性とその方法について簡単にまとめていきたいと思います。
LINE Botとは?
本記事でいうLINE Botとは、LINE公式アカウントと自社のサーバーを Messaging API で連携し、LINE公式アカウントへのユーザーのアクションに対して自動的に応答してくれる Bot のことです。
ざっくり言うと、
- ユーザーが公式アカウントに何かしらアクションを行う(友達登録、メッセージを送るなど)
- LINE から自社のサーバー(Webhook URL)にイベントが飛んでくる
- サーバー側で内容を処理して、Messaging API で返信を返す
という流れで動きます。
リッチメニューとは?
リッチメニューとは、LINE公式アカウントのトーク画面の下部に固定表示できる大きめのメニューエリアです。
さまざまなアクションを設定することができ、よりリッチなユーザー体験を提供できます。
リッチメニューを押すことで、LIFFアプリを起動したり、自社のサーバー(Webhook URL)にイベントを送信することができます。
リッチメニューを入口として、ユーザーに対してさまざまな機能やサービスを展開していくことが可能です。
リッチメニューを切り替える
リッチメニューを1つ作って全てのユーザーに対して固定表示しておくだけでもサービスとして最低限成り立ちます。
ですが、実際に運用を進めていく場合 「1パターンだけでは足りない」 場面がすぐに出てきます。
例えば、次のようなケースです。
1. ユーザーの状態に応じて出したいメニューが変わる
- 初回利用時は「アカウント連携」「サービスについて」など、案内系のボタンを表示したい
- アカウント連携後は、「通常のサービスメニュー」を表示したい
このように、
「ユーザーの状態」で必要な導線が違う場合、 1つのメニューを出し続けると、とても使いにくい構成になってしまいます。
2. ユーザーの属性によって必要な機能が違う
サービスによっては、ユーザーの属性に応じて提供する機能が異なるケースもあります。
権限による制御の例:
- 現場管理者 / 管理者向けメニュー
- 一般作業者 / スタッフ向けメニュー
- 社外担当者 / 外部パートナー向けメニュー など
提供プランによる制御の例:
- Proプラン / 全ての機能をメニューで表示
- Basicプラン / 一部機能だけメニューで表示
- お試しプラン / 一部機能 + 上位プランへの案内をメニューで表示
この場合、全ユーザー共通のリッチメニューにしてしまうと、
- ボタンが増えすぎて分かりづらい
- 権限のないユーザーに不要なメニューが見えてしまう
といった問題が出てくるため、ユーザーの属性ごとにリッチメニューを出し分ける方が自然です。
3. 期間限定のキャンペーンやお知らせを出したい
- キャンペーン期間中だけ「応募」「エントリー」ボタンを追加したい
- メンテナンス期間中は「メンテナンス中のお知らせ」ボタンを目立つ位置に置きたい
といったように、特定の期間だけ見せたいメニューも運用していれば出てくると思います。
このように、実際のサービス運用を考えていくと、複数パターンのリッチメニューを切り替えながら運用する必要性が見えてきます。
こうした要件を満たすために、Messaging APIなどを利用してリッチメニューを切り替える必要があります。
次のセクションでは、Messaging API を使ってリッチメニューを切り替える方法を紹介していきます。
Messaging APIでリッチメニューを切り替える
ここからは、実際に Messaging API を使ってリッチメニューを切り替える方法について紹介していきます。
LINE のリッチメニューを API から制御する場合、主に次の 2 つを押さえておく必要があります。
-
デフォルトリッチメニューの変更
- 友だち追加している「全ユーザー」に共通で表示されるリッチメニューを切り替える
-
ユーザー単位のリッチメニューの変更
- ユーザーごとに個別にリッチメニューを設定する
実際の運用で「ユーザーの状態別」「ユーザーの属性別」にメニューを出し分けたい場合は、ユーザー単位のリッチメニューを使う必要があります。
リッチメニュー制御で利用するAPI
Messaging API の中で、リッチメニューまわりでよく利用するのは以下のエンドポイントです。
- リッチメニューを作成
POST /v2/bot/richmenu - リッチメニュー画像をアップロード
POST /v2/bot/richmenu/{richMenuId}/content - デフォルトのリッチメニューを設定
POST /v2/bot/user/all/richmenu/{richMenuId} - ユーザーにリッチメニューを紐付け
POST /v2/bot/user/{userId}/richmenu/{richMenuId} - ユーザーとリッチメニューの紐付けを解除
DELETE /v2/bot/user/{userId}/richmenu - 複数ユーザーにまとめて紐付け / 解除
POST /v2/bot/richmenu/bulk/link
POST /v2/bot/richmenu/bulk/unlink
LINE公式ドキュメント:https://developers.line.biz/ja/docs/messaging-api/rich-menus-overview/
ユーザー単位のリッチメニューの切り替えイメージ
ユーザー単位のリッチメニューは、「どのユーザーに、どのリッチメニューIDを紐付けるか」 を API で指定する仕組みです。
ざっくりとした流れは以下の通りです。
- あらかじめ、必要なパターンのリッチメニューを作成しておく
(例:ONBOARDING_MENU,NORMAL_MENU,ADMIN_MENUなど) - 作成後に払い出された
richMenuIdを、DBなどに保存しておく - Webhook でユーザーの状態変化を検知したタイミングで、
- 「このユーザーにはどのメニューを出したいか」を判定する
- 判定結果に応じた
richMenuIdを、Messaging API でユーザーに紐付ける
「リッチメニューを切り替える」というより、「ユーザーとリッチメニューの紐付けを更新している」 イメージに近いです。
(更新しない場合は、デフォルトのリッチメニューが表示され続ける。)
ユーザーにリッチメニューを紐付けるサンプル(curl)
最低限の例として、特定ユーザーに特定のリッチメニューを紐付ける curl は以下のようになります。
curl -X POST \
"https://api.line.me/v2/bot/user/{userId}/richmenu/{richMenuId}" \
-H "Authorization: Bearer ${LINE_CHANNEL_ACCESS_TOKEN}"
- {userId}:LINEのユーザーID(Webhookで受け取れる source.userId)
- {richMenuId}:事前に作成しておいたリッチメニューのID
サーバー側では、Webhook でユーザーのアクションを受け取ったタイミングで、
- ユーザー情報(状態・属性)を取得
- 表示したいリッチメニューを決定
- 上記 API を呼び出す
という流れでリッチメニューの切り替えを実施していきます。
Webhookを利用した状態別のリッチメニュー切り替え例
ここからは具体例として、初回利用時はオンボーディング用メニューを表示し、
アカウント連携が完了したら通常メニューに切り替えるパターンを例にしてみます。
想定シナリオ
- 友だち追加直後:
→ LINE公式アカウントの デフォルトリッチメニュー として「アカウント連携」「サービスについて」などを配置したオンボーディング用メニューを表示 - アカウント連携完了後:
→ ユーザー単位のリッチメニューとして、実際に使う機能を並べたNORMAL_MENUに切り替え
このとき、ユーザーの状態は以下のように管理していきます。
-
activate_status-
ONBOARDING(初回利用中/アカウント連携前) -
COMPLETED(アカウント連携完了)
-
アカウント連携自体は、LINEの アカウント連携(Account Link)機能 を使い、
連携完了時に Webhook に送られてくる accountLink イベント を利用する。
処理フローのイメージ
テキストでフローを書くと、だいたいこんな感じです。
- ユーザーが LINE 公式アカウントを友だち追加する
-
followイベントが Webhook に飛んでくる - サーバー側でユーザー情報を登録し、
activate_status = ONBOARDINGにする- この時点では、デフォルトリッチメニュー(オンボーディングメニュー)が表示される
- ユーザーに対して挨拶メッセージなど送信してアカウント連携を促す
- ユーザーがリッチメニューの「アカウント連携」ボタンから LIFF などを起動し連携処理を行う
- アカウント連携が完了すると、LINE から Bot に
accountLinkイベントが送られる -
accountLinkイベントを受け取ったタイミングで、- アプリ側のユーザーと LINE のユーザーを紐付け
-
activate_status = COMPLETEDに更新 -
NORMAL_MENUのrichMenuIdを取得し、ユーザー単位リッチメニューとして link する
Webhookハンドラの実装イメージ(Node.js)
Node.js + Express で Webhook を受けて処理するイメージを簡易的に書くと、以下のようになります。
(DB アクセスはリポジトリのラッパー関数にまとめている想定です)
import express from "express";
import { linkRichMenuToUser } from "./lineClient.js";
import {
findUserByLineUserId,
createUser,
updateUserActivateStatus,
linkAppAccountAndLineUser, // アプリ側アカウントとLINEユーザーの紐付け用
} from "./repositories/userRepo.js";
import { findRichMenuByCode } from "./repositories/richMenuRepo.js";
const app = express();
app.use(express.json());
app.post("/webhook", async (req, res) => {
const events = req.body.events || [];
await Promise.all(events.map(handleEvent));
res.sendStatus(200);
});
async function handleEvent(event) {
switch (event.type) {
case "follow":
return handleFollow(event);
case "accountLink":
return handleAccountLink(event);
case "postback":
// 必要に応じてここで postback を処理する
return;
default:
return;
}
}
async function handleFollow(event) {
const lineUserId = event.source.userId;
let user = await findUserByLineUserId(lineUserId);
if (!user) {
// 友達登録時にユーザー登録を行う
user = await createUser({
lineUserId,
activateStatus: "ONBOARDING",
});
} else {
// 再度友だち追加された場合など、必要に応じて状態をリセットしてもよい
// リセットする場合はリッチメニューを解除してデフォルトが表示されるようにする
// await updateUserActivateStatus(user.id, "ONBOARDING");
}
// この時点では link は何もしない
// → デフォルトリッチメニュー(オンボーディング用)が表示される
// ユーザーに対して何かしら返信をするならここに記載していく
}
async function handleAccountLink(event) {
const lineUserId = event.source.userId;
if (!lineUserId) return;
const link = event.link; // { result: "ok" | "failed", nonce: "xxxx" } のイメージ
if (!link || link.result !== "ok") {
console.warn("account link failed:", link);
return;
}
const nonce = link.nonce; // 事前にアプリ側で発行していた nonce
// nonce を使ってアプリ側のアカウントと LINE ユーザーを紐付ける
// 具体的な実装はアプリごとの仕様に依存
await linkAppAccountAndLineUser({ nonce, lineUserId });
await completeActivate(lineUserId);
}
async function completeActivate(lineUserId) {
const user = await findUserByLineUserId(lineUserId);
if (!user) {
console.error("user not found:", lineUserId);
return;
}
// 状態を更新(アカウント連携完了)
await updateUserActivateStatus(user.id, "COMPLETED");
// 通常メニュー用リッチメニューを取得
const normalMenu = await findRichMenuByCode("NORMAL_MENU");
if (!normalMenu) {
console.error("NORMAL_MENU not found");
return;
}
// ユーザーに通常メニューを紐付け
await linkRichMenuToUser(lineUserId, normalMenu.richMenuId);
}
※サンプルコードを簡単にするために、Webhook の署名検証やエラーハンドリング、返信処理などは省略しています。
流れのまとめ
この構成にしておくと、挙動は次のようになります。
- 友だち追加直後
- 友だち追加をすると follow イベントがWebhookに飛ぶ
- サーバー側でユーザー登録(activate_status = ONBOARDING)
- 「デフォルトリッチメニュー」を表示したままにする
- アカウント連携完了後
- Account Link 成功時に accountLink イベントがWebhookに飛ぶ
- handleAccountLink → completeActivate を通じて
- アプリ側アカウントと LINE ユーザーを紐付け
- activate_status = COMPLETED に更新
- NORMAL_MENU の richMenuId をユーザー単位リッチメニューとして link
- 以降、そのユーザーには NORMAL_MENU が表示される(デフォルトより優先)
実際の実装では、
- アカウント連携処理で利用する nonce をどのテーブルで管理するか
- どのタイミングで「アカウント連携完了」とみなすか
- エラー時にどんなメッセージ/メニューを出すか
などをアプリ要件に合わせて設計していく必要はありますが、大枠の「Webhookでのトリガー起動でユーザー単位リッチメニューを切り替える」流れはこのイメージで押さえておけます。
まとめ
本記事では、LINE のリッチメニューについて、ざっくりと概要をまとめました。
リッチメニューを 1 パターン固定で使うだけでもサービスは動きますが、
- ユーザーの状態(連携前 / 連携後)
- ユーザーの属性(権限やプラン)
- キャンペーンなどの期間限定施策
に応じてリッチメニューを切り替えられるようにしておくと、
ユーザーにとっても、運用側にとっても扱いやすい構成にできます。
実際の案件では、どのユーザーに今どのリッチメニューを紐づけているか管理したり、
一度ブロックされてから再度友だち登録がされた場合など通常利用ではないケースの考慮などが必要になってきます。
今後は、より詳しい運用パターンやベストプラクティスについても調べていきたいと思います。