IT技術ブログ

LINEミニアプリの作り方予約画面を作る手順

LINEミニアプリで予約画面を作る手順です。チャネルの作成、開発用・審査用・本番用の内部チャネル、liff.initとIDトークンの検証、予約確認のサービスメッセージ、パーマネントリンク、審査の前の準備をまとめました。

この記事の結論:LINEミニアプリは、LINEの中で開くWebの画面です。予約画面を作る順番は、LINEミニアプリチャネルを作る、開発用・審査用・本番用の3つの内部チャネルにそれぞれのURLを置く、liff.init()で始める、IDトークンをサーバーで検証して予約を受ける、確認をサービスメッセージで送る、パーマネントリンクで配る、審査を申請する、の7つです。詰まりやすいのは、内部チャネルごとにLIFF IDが違うこと、プロフィールをそのままサーバーへ送ってはいけないこと、サービスメッセージは認証済ミニアプリでしか本番で送れないこと、審査に1〜2週間かかることです。この記事のコードは構文を確かめただけで、本物のLINEでは動かしていません。

この記事の内容のご相談は株式会社bundlyzeへbundlyzeのシステム開発について見てみる

この記事は、ジム、スタジオ、整体院、結婚式場のフェア予約などで、予約の画面をLINEミニアプリとして作りたい開発の担当者に向けて書きました。「LINEミニアプリにするか、自社アプリにするか」を決める段階なら、ジムの会員アプリとLINEミニアプリの決め方が先です。トークのボタンだけで予約を受ける作りはLINE予約システムの作り方に、公式アカウントのWebhookとメッセージの送り方はMessaging APIの始め方に書いたので、ここでは繰り返しません。この記事が扱うのは、名前や電話番号を入力してもらう予約の画面を、LINEミニアプリとして作って出すまでです。

載せたコードは、2026年11月3日に手元のNode.jsで構文を確かめただけです。LINEミニアプリチャネルは作っておらず、本物のLINEのAPIには一度も送っていません。 スマホでも開いていません。コンソールの操作と仕様は、LINE Developersのドキュメントで確かめた内容です。確かめられなかったことは、後半の「確かめられなかったこと」にまとめます。

LINEミニアプリは、LIFFの上で動くWebアプリ

LINEミニアプリは、LIFF(LINE Front-end Framework)の上で動くWebアプリです。お客様はアプリを入れずに、LINEの中で予約の画面を開けます。HTML5のほとんどの仕様が使えるので、作り方はふつうのWebの画面とほぼ同じです。

LINE Developersのドキュメントには、LIFFとLINEミニアプリのブランド統合を予定していると書かれています。統合によってLIFFはLINEミニアプリに統合されるので、LIFFアプリを新しく作るときは、LINEミニアプリとして作ることを勧めています。詳しい予定は2025年2月12日のニュースに書かれていますが、このニュースのページは開けませんでした。

もう1つ、Messaging APIのチャネルには、LIFFアプリを追加できません。LIFFのリリースノートでは、2020年2月5日からそうなったとされています。公式アカウントのチャネルとは別に、LINEミニアプリチャネルを作ります。公式アカウントのリッチメニューやメッセージからは、リンクで開けます。

LINEミニアプリは、審査に通っているかどうかで2つに分かれます。

未認証ミニアプリ 認証済ミニアプリ
なり方 チャネルを作るとこの状態になる LINEヤフーの認証審査に通る
ヘッダーの表示 タイトルとエンドポイントURLのドメイン 認証バッジ、タイトル、LINEミニアプリ名
サービスメッセージ 開発用の内部チャネルで試せるだけ 本番で送れる
ホーム画面へのショートカット 使えない 使える
Custom Path(独自の文字列のURL) 使えない 申請して使える
チャネル同意の簡略化 開発用と審査用でのみ動く 動く
LINEの検索、ホームタブの「最近の履歴」 出ない 出る

LIFFアプリとの違いで、予約の画面に関わるのは次の3つです。

  • アクションボタンは消せない。 ヘッダーのアクションボタン(シェアなど)は常に出ます。LIFFのモジュールモードは使えません
  • 画面サイズはFullだけ。 下から少しだけ出る形は選べません
  • 1つのチャネルにWebアプリは1つ。 予約、会員証、問い合わせのように画面を分けたいときは、1つのWebアプリの中でページを分けます

予約画面は7つの手順で作る

作る順番は、前の手順ができていないと次を試せない順にしています。

手順 すること 詰まる所
1 LINEミニアプリチャネルを作る プロバイダーは後から変えられない。プライバシーポリシーURLは作った後に入れることがある
2 3つの内部チャネルにURLを置き、手元で開く 内部チャネルごとにLIFF IDが違う。serveは公開中のアプリで使わない
3 liff.init()で始める URLの書き換えはinitが終わってから
4 IDトークンをサーバーで検証して予約を受ける プロフィールをそのまま送らない。名前は同意がないと入らない
5 予約の確認をサービスメッセージで送る 認証済だけ。宣伝は送れない。トークンは送るたびに入れ替わる
6 パーマネントリンクで配る LIFF URLではなく、ページごとのリンクを使う
7 審査を申請して公開する 1〜2週間かかる。予約の流れはテストの手順を書いて渡す

手順1. チャネルは、コンソールで「LINEミニアプリ」として作る

LINEミニアプリチャネルは、LINE Developersコンソールで作ります。Messaging APIのチャネルのように、公式アカウントの側から作るものではありません。

  1. LINE Developersコンソールで、プロバイダーを選ぶ
  2. [チャネル設定]>[新規チャネル作成]>[LINEミニアプリ]の順に押す
  3. 次の項目を入れる
  4. 会社を代表する権限があることの表明にチェックを付け、[作成]を押す
  5. 「情報利用に関する同意について」を読み、[同意する]を押す
項目 必須 予約画面で気をつけること
サービスを提供する地域 必須 日本、タイ、台湾から選ぶ。国ごとにチャネルを作る
チャネルアイコン 任意 同意の画面やサービスメッセージのフッターに出る
チャネル名 必須 「LINE」やそれに似た文字は入れられない。認証済ミニアプリのヘッダーに出る
チャネル説明 必須 審査はこの文をもとに行われる。何ができるサービスかを具体的に書く
メールアドレス 必須 審査の結果や大事なお知らせが届く
プライバシーポリシーURL 説明を参照 作るときに入れられるのは認証プロバイダーだけ。それ以外は作った後に[チャネル基本設定]で入れる
サービス利用規約URL 任意 同意の画面に出る
規約への同意と所在国の表明 必須 所在国・地域は、作った後に編集できない

チャネルを作れないときは、コンソールにログインしているビジネスIDをLINEアカウントと連携します。

なお、「LINEミニアプリのユーザーに表示される設定一覧」のページには、[チャネル名]と[チャネル説明]は英語で入れ、日本語の名前と説明は[多言語対応]で入れるよう書かれています。同意の画面では、お客様がLINEで選んでいる言語のものが出ます。

いちばん大事なのは、Messaging APIのときと同じくプロバイダーです。作ったチャネルは、後から別のプロバイダーへ移せません。ユーザーIDはプロバイダーごとに違う値になるので、公式アカウントのMessaging APIのチャネルと同じプロバイダーに置かないと、ミニアプリで予約した人と、公式アカウントの友だちを同じ人として結べません。プロバイダーの選び方はMessaging APIの始め方の手順1に書きました。

チャネル説明は、ドキュメントの良い例にならって、何ができるかまで書きます。たとえば「〇〇スタジオの体験レッスンとクラスを、日時を選んで予約できるサービスです」のような形です。開発を外の会社に頼んでいる場合は、サービス事業主の名前と開発担当の会社の名前を、チャネル説明に書く決まりです。ミニアプリで受け取ったお客様の情報をほかの会社に渡すなら、その会社の名前も書きます。開発担当とサービス事業主が違うときは、チャネル説明とプライバシーポリシーURLを設定しないと審査を通過できないとされています。

手順2. 3つの内部チャネルにURLを置き、手元で開く

コンソールではチャネルが1つに見えますが、中は3つの内部チャネルでできています。

内部チャネル 使う人 ステータス 置くもの
開発用 権限を渡して承認されたTesterだけ 常に「開発中」 作っている途中の画面
審査用 LINEヤフーの審査担当だけ 常に「開発中」 審査に出す版
本番用 お客様 常に「公開中」 公開する版

内部チャネルごとにLINEミニアプリ(LIFFアプリ)が1つずつ用意されていて、LIFF IDも別です。[ウェブアプリ設定]タブで、それぞれのエンドポイントURLを入れます。URLはhttpsで始め、#は付けられません。審査を申請する前に審査用のURLへ、公開する前に本番用のURLへ、その版をデプロイしておきます。

予約画面のURLは、たとえば次のように分けておくと、取り違えが減ります。これは経験則で、公式の手順ではありません。

開発用  https://dev.example.com/booking
審査用  https://review.example.com/booking
本番用  https://www.example.com/booking

開発用と審査用のURLには、ベーシック認証をかけられます。使えるのは、ステータスが「審査前」か「審査中」で、LIFFブラウザで開いたときだけです。ドキュメントは、ベーシック認証は簡単な制限で、安全さを保証するものではないとしています。審査用にかけたときは、審査の申請でユーザー名とパスワードを伝えます。

設定がいつ内部チャネルに写るかも、先に知っておきます。

  • 未認証ミニアプリの間は、 コンソールで設定すると、開発用の内容が本番用にも写ります。ただし、サービスメッセージのテンプレートとチャネル同意の簡略化は、審査に通るまで写りません
  • 認証済ミニアプリになった後は、 コンソールの変更は開発用だけに入ります。審査用には審査の開始時に、本番用には公開の時に写ります

未認証の間は、開発用のつもりで変えたコンソールの設定が、そのまま本番用にも入る、ということです。本番用のURLに置いた版も、そのまま公開されている前提で扱います。

手元で開くときは、LIFF CLIのserveを開発用だけに使う

LINEの中で開くには、エンドポイントURLがHTTPSである必要があります。手元の開発サーバーで試すときは、LIFF CLIのserveコマンドが使えます。手元の開発サーバーの前にHTTPSのプロキシを立て、LIFFアプリのエンドポイントURLをそのURLに書き換えるコマンドです。

bash
npm install -g @line/liff-cli
mkcert -install                       # 最初の1回だけ。手元に認証局を作る
mkcert localhost                      # localhost.pem と localhost-key.pem を作る
liff-cli channel add <チャネルID>      # チャネルシークレットを聞かれる
liff-cli serve --liff-id <開発用のLIFF ID> --url http://localhost:3000/

localhost.pemとlocalhost-key.pemを作った場所でserveを実行します。手元のパソコンの外から届くURLが要るときは、--proxy-type ngrokとNGROK_AUTHTOKENの設定でngrokを使えます。

ドキュメントは、公開済みのLIFFアプリではserveを実行しないよう書いています。エンドポイントURLが書き換わり、お客様が開けなくなるためです。LINEミニアプリでは、開発用の内部チャネルのLIFF IDだけに使います。なお、LIFF CLIのページにLINEミニアプリチャネルでの使い方の説明はなく、ミニアプリの内部チャネルでchannel addとserveがそのまま動くかは確かめられていません。

ひな形が欲しいときは、npx @line/create-liff-appでReactやNext.jsなどの開発環境を作れます。

手順3. liff.init()は、ページを開くたびに最初に呼ぶ

SDKは、npmの@line/liffかCDNで読み込みます。liff.init()は、ページを開くたびに呼ぶ必要があります。同じアプリの中でも、新しいページを開いたら呼び直します。

main.jsjs
// main.js:予約画面の入口。ページを開くたびに liff.init() を呼ぶ
import liff from "@line/liff";

// 内部チャネル(開発用・審査用・本番用)ごとにLIFF IDが違うので、ビルドの環境変数で切り替える
const LIFF_ID = import.meta.env.VITE_LIFF_ID;

async function start() {
  try {
    await liff.init({ liffId: LIFF_ID });
  } catch (err) {
    showError("画面を開けませんでした。LINEから開き直してください。");
    console.error(err.code, err.message);
    return;
  }
  // URLの書き換えや画面の切り替えは、init が終わってから行う
  renderBookingForm();
}

start();

showErrorとrenderBookingFormは、自分の画面に合わせて書く関数です。LIFF IDはコードに直接書かず、内部チャネルごとに環境変数で入れます。一部の内部チャネルだけで開けないときは、まずその内部チャネルのLIFF IDと、liff.init()に渡したLIFF IDが合っているかを見ます。

initの前後で気をつけることは、ドキュメントに3つ書かれています。

  • エンドポイントURLの下で動かす。 動きが保証されるのは、エンドポイントURLと同じか、その下の階層のURLだけです。https://example.com/bookingなら、/booking/slots/は対象で、/や/other/は対象外です
  • URLの書き換えはinitの後。 history.pushState()などでの書き換えや、サーバーでのリダイレクトは、liff.init()が終わってから行います
  • 最初のURLをログに送らない。 最初に開いたURLにはお客様のアクセストークンが入ります。v2.11.0以降はinitの完了時にURLから取り除かれるので、アクセス解析のページビューはinitの後に送ります

LIFFブラウザの中では、ログインはinitで自動で行われ、liff.login()は使えません。外部ブラウザでも開かせるなら、withLoginOnExternalBrowser: trueを付けると自動でログインに進みます。LINEミニアプリは、2025年10月1日から外部ブラウザでも使えるようになったと、仕様のページに書かれています。LINEを入れていない人が開いたときにどう見えるかは、手元で確かめていません。

会社サイトの関連ページ公式LINEの運用と構築

手順4. サーバーにはIDトークンを送り、検証してから予約を受ける

予約の画面で取りたいのは、誰の予約かを示すLINEのユーザーIDです。ここで間違えやすいのが、liff.getProfile()やliff.getDecodedIDToken()で取ったプロフィールを、そのままサーバーに送ることです。ドキュメントは、これらをサーバーに送らないよう警告しています。ユーザー情報を正しく扱わないと、なりすましなどの攻撃に弱くなるとしています。

サーバーには、liff.getIDToken()で取ったIDトークンを送ります。サーバーはそれをLINEに送って検証し、ユーザーIDを受け取ります。

submit.jsjs
// submit.js:予約を送る。プロフィールではなくトークンをサーバーへ渡す
import liff from "@line/liff";

export async function submitBooking(form) {
  // 名前を使うなら、トークンを取る前に profile の権限を確かめる
  const status = await liff.permission.query("profile");
  if (status.state === "prompt") {
    await liff.permission.requestAll();
  }

  const idToken = liff.getIDToken(); // openid の権限が要る。取れないときに備えて下で確かめる
  const liffAccessToken = liff.getAccessToken(); // サービスメッセージ用
  if (!idToken) throw new Error("no id token");

  const res = await fetch("/api/bookings", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      idToken,
      liffAccessToken,
      slotId: form.slotId,
      phone: form.phone,
    }),
  });
  if (!res.ok) throw new Error(`booking failed: ${res.status}`);
  return res.json();
}

最初に権限を確かめているのには理由があります。日本の新しいLINEミニアプリチャネルでは、「チャネル同意の簡略化」が常に有効です(2026年1月8日以降に作ったチャネル)。この機能で最初に同意を省けるのは、ユーザーIDの取得(openid)だけです。表示名などのprofileは、この時点ではまだ同意を得ていないので、そのまま取ったIDトークンには名前が入りません。ドキュメントは、プロフィールをサービスの登録に使う作りなら、トークンを取る前にliff.permission.query()とliff.permission.requestAll()で同意を求めるよう書いています。requestAll()はLINEミニアプリでだけ動き、すべて同意済みのときに呼ぶとエラーになるので、先にquery()でpromptかどうかを見ます。

もう1つ、お客様が追加の権限を許可すると、LIFFのアクセストークンが新しくなり、それより前のものは使えなくなります。上のsubmit.jsで、権限を確かめてからliff.getAccessToken()を呼んでいるのはそのためです。なお、チャネル同意の簡略化が動くのは、認証済ミニアプリでLIFF SDKがv2.13.x以降のときで、未認証の間は開発用と審査用でだけ動きます。

予約の画面では、名前は入力欄で聞き、LINEの表示名は使わない作りにすると、この同意の画面を出さずに済みます。ただし、認可フローのページの表では、liff.getIDToken()はemailの権限が要るメソッドにも挙がっています。[ウェブアプリ設定]のScopeでemailを有効にしているなら、ここで許可を求める画面が出ることがあります。LINEの表示名は本名とは限らないので、予約の台帳にはもともと向きません。

サーバーの側は、IDトークンを検証するエンドポイントに送ります。

verify.mjsjs
// verify.mjs:IDトークンをLINEのエンドポイントで検証し、ユーザーIDを取り出す
const CHANNEL_ID = process.env.LINE_MINIAPP_CHANNEL_ID; // 内部チャネルごとに確かめる

export async function verifyIdToken(idToken) {
  const res = await fetch("https://api.line.me/oauth2/v2.1/verify", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({ id_token: idToken, client_id: CHANNEL_ID }),
  });
  const body = await res.json();
  if (!res.ok || body.error) {
    // 例:{ "error": "invalid_request", "error_description": "IdToken expired." }
    throw new Error(`verify failed: ${res.status} ${body.error_description}`);
  }
  if (body.aud !== CHANNEL_ID) throw new Error("aud mismatch");
  return { userId: body.sub, name: body.name ?? null }; // name は profile の同意がないと入らない
}

client_idには、期待するチャネルIDを入れます。検証に通ると、sub(ユーザーID)、aud(チャネルID)、exp(有効期限)などが返ります。nameとpictureはprofile、emailはemailの同意がないと入りません。失敗したときはerror_descriptionに理由が入ります。

error_description 意味 見直すこと
Invalid IdToken. 形が正しくないか、署名が無効 画面から送った値が途中で切れていないか
Invalid IdToken Issuer. LINE以外で作られた 送られてきた値そのもの
IdToken expired. 有効期限切れ IDトークンは発行から1時間で切れる。画面を長く開いたままにしていないか
Invalid IdToken Audience. client_idと合わない 内部チャネルの取り違え

予約の表には、検証して得たsubをLINEのユーザーIDとして残します。画面から送られた枠のIDや電話番号は、サーバーでもう一度確かめます。枠の確保と二重予約の防ぎ方はLINE予約システムの作り方の手順5と二重予約の防止に、LINEの人と顧客の記録の結び方は公式LINE連携の進め方に書きました。

手順5. 予約の確認は、サービスメッセージで送る

サービスメッセージは、LINEミニアプリでの操作に対する確認や応答を、LINEの中で知らせる機能です。日本では「LINEミニアプリ お知らせ」というトークルームに届きます。公式アカウントのトークではありません。

送れる内容と送れない内容は、決まっています。

例
送れる:操作の確認 予約を受け付けた
送れる:操作の結果 チェックインが済んだ
送れる:リマインド 予約の前日の案内
送れない 値下げ、特典、新商品、割引クーポン、キャンペーンなどの宣伝やイベントの案内
送れない LINEミニアプリの上での操作ではないものへの通知

決まりに外れたものを送ると、サービスメッセージのAPIを一定の期間使えなくなり、くり返すとLINEミニアプリが削除されることがあるとされています。次のフェアやキャンペーンの案内は、公式アカウントから送ります。

送るまでの流れ

  1. コンソールの[サービスメッセージテンプレート]タブで、予約などのカテゴリからテンプレートを選んで追加する(1チャネルに20個まで)。[使用事例]には送る場面を正しく書く
  2. 予約のときに、画面でliff.getAccessToken()を取り、サーバーへ送る(上のsubmit.js)
  3. サーバーで、チャネルアクセストークンを発行する
  4. LIFFのアクセストークンを使って、サービス通知トークンを発行する
  5. サービス通知トークンで1通目を送り、返ってきた新しいトークンを保存する
  6. 前日のリマインドなど後の連絡は、保存したトークンで送り、また新しいトークンを保存する

チャネルアクセストークンは、ステートレス(15分だけ有効)か短期のものを使います。LINEミニアプリチャネルでは、長期のトークンとv2.1は使えません。ドキュメントが勧めているのはステートレスです。発行の数に制限がなく、使うたびに発行できます。トークンは内部チャネルごとに発行し、審査用や本番用から送るときに、開発用のトークンを使わないよう注意書きがあります。

notify.mjsjs
// notify.mjs:予約を受けた直後に、サービスメッセージで確認を送る
const API = "https://api.line.me";

async function statelessToken() {
  const res = await fetch(`${API}/oauth2/v3/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.LINE_MINIAPP_CHANNEL_ID,
      client_secret: process.env.LINE_MINIAPP_CHANNEL_SECRET,
    }),
  });
  if (!res.ok) throw new Error(`token ${res.status}`);
  return (await res.json()).access_token; // 15分だけ有効。使うたびに発行してよい
}

async function post(path, token, payload) {
  const res = await fetch(`${API}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
    body: JSON.stringify(payload),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${path} ${res.status} ${body.message}`);
  return body;
}

// 1回目:LIFFのアクセストークンから、サービス通知トークンを1つだけ発行して送る
export async function sendConfirmation(liffAccessToken, params) {
  const token = await statelessToken();
  const issued = await post("/message/v3/notifier/token", token, { liffAccessToken });
  const sent = await post("/message/v3/notifier/send?target=service", token, {
    templateName: process.env.SERVICE_TEMPLATE_CONFIRM, // 例の形:{テンプレート名}_ja
    params, // キーはテンプレートごとに決まっている
    notificationToken: issued.notificationToken,
  });
  // 次の連絡(前日のリマインドなど)に使うので、予約の行に保存する
  return { notificationToken: sent.notificationToken, remainingCount: sent.remainingCount };
}

// 2回目以降:保存しておいた最新のサービス通知トークンで送る
export async function sendFollowUp(saved, templateName, params) {
  if (!saved.notificationToken || saved.remainingCount <= 0) return null;
  const token = await statelessToken();
  const sent = await post("/message/v3/notifier/send?target=service", token, {
    templateName,
    params,
    notificationToken: saved.notificationToken,
  });
  return { notificationToken: sent.notificationToken, remainingCount: sent.remainingCount };
}

.envには値を書かず、名前だけを決めておきます。値はgitに入れないファイルか、本番の環境変数に置きます。

.env(gitに入れない。値は内部チャネルごとに各自のものを入れる)
VITE_LIFF_ID=
LINE_MINIAPP_CHANNEL_ID=
LINE_MINIAPP_CHANNEL_SECRET=
SERVICE_TEMPLATE_CONFIRM=
SERVICE_TEMPLATE_REMIND=

テンプレートの名前は、コンソールの[API用テンプレート名]に出る{template name}_{BCP 47 language tag}の形で、日本語なら末尾が_jaです。paramsのキーはテンプレートごとに決まっていて、必須のものが抜けるとエラーになります。この記事では実際のテンプレート名とキーを確かめていないので、コードでは環境変数と引数にしています。

サービス通知トークンの扱いで、守ることは次のとおりです。

  • 1つの操作で送れるのは5回まで。 トークンは発行から1年間有効で、残りの回数はremainingCountで分かります
  • 送るたびにトークンが入れ替わる。 返ってきたnotificationTokenを、予約の行に上書きして保存します。expiresInとremainingCountが0で返ったときは、送れたけれどトークンを更新できなかった、という意味です
  • 1つのLIFFのアクセストークンで発行できるのは1つだけ。 後の連絡のために、同じアクセストークンから発行し直してはいけません。短い間に続けて発行を頼むと400になります
  • LIFFのアクセストークンは早めに使う。 有効期間は12時間ですが、お客様が画面を閉じると無効になることがあります。追加の権限を許可してもらったときも、それより前のトークンは無効になります。予約を送った処理の中で、すぐに1通目まで送ります
  • トークンは1人に結びつく。 ある人のトークンで、別の人には送れません

前日のリマインドは、定期実行で予約の表を見てsendFollowUpを呼ぶ形になります。定期実行の作りと二重送信の防ぎ方は、前日リマインドの記事と同じ考え方です。

未認証ミニアプリの間は、サービスメッセージは開発用の内部チャネルで試せるだけです。テンプレートを追加する画面では、JSONで値を入れてプレビューを見て、ログインしている自分のLINEにテストのメッセージを送れます。テンプレートをAPIで本番に使えるのは、審査に通った後です。審査の最中は、新しいテンプレートを追加できません。

手順6. 共有は、パーマネントリンクで行う

予約の画面を、Webサイト、メール、QRコード、公式アカウントのリッチメニューやメッセージから開いてもらうときは、パーマネントリンクを使います。ドキュメントは、ページをシェアするときはLIFF URLではなくパーマネントリンクを使うよう書いています。

パーマネントリンクは、次の式で作ります。

パーマネントリンク = LIFF URL +(開きたいページのURL − エンドポイントURL)

エンドポイントURL  https://www.example.com/booking
開きたいページ     https://www.example.com/booking/fair?date=2026-12-06
LIFF URL           https://miniapp.line.me/<本番用のLIFF ID>
パーマネントリンク https://miniapp.line.me/<本番用のLIFF ID>/fair?date=2026-12-06

画面の中で作るときは、liff.permanentLink.createUrlBy()が使えます。ドキュメントの例はどれもエンドポイントURLの末尾に/がない形で、上の例もそれに合わせました。手順2のように末尾に/を付けたエンドポイントURLでどうなるかは確かめていないので、配る前にcreateUrlBy()で作った値と見比べます。LINEミニアプリのLIFF URLは、2023年12月13日からhttps://miniapp.line.me/{liffId}の形になりました。前のhttps://liff.line.me/{liffId}でも開けるので、配ったQRコードはそのまま使えます。

気をつけるのは、リンクの中のLIFF IDも内部チャネルごとに違うことです。チラシやリッチメニューに載せるのは、本番用のLIFF IDのリンクです。認証済ミニアプリになれば、https://miniapp.line.me/<独自の文字列>の形のCustom Pathを申請できます。日本では申請から使えるまで1〜2週間ほどかかり、一度決めた文字列は原則として変えられません。審査の前にも申請できますが、設定されるのは審査に通った後です。

LINEを入れていない人がリンクを開くと、LINEで開くよう案内する画面が出ます。そこからWebブラウザでエンドポイントURLのページを開くこともできます。

手順7. 審査の前に用意しておくこと

認証済ミニアプリにするには、コンソールの[審査申請]タブから申請します。審査はおおむね1〜2週間かかり、却下されると再申請と再審査にさらに数日かかります。完了の日は指定できません。予約を受け始める日が決まっているなら、そこから逆算して申請します。

申請の前に、ドキュメントとLINEミニアプリポリシーに書かれていることを、予約画面に当てはめて並べます。「推奨」と書いた行は、ポリシーの推奨事項に入っているものです。

用意するもの どこに 予約画面での例
チャネル説明 [チャネル基本設定] 何を予約できるサービスかを具体的に書く
プロバイダー名 [プロバイダー設定] サービスを提供する会社と同じ名前にする
プライバシーポリシーURL [チャネル基本設定] ユーザー情報を取る会社を、プロバイダー名と同じ会社にする
審査用と本番用の中身 各エンドポイントURL 審査用のURLと本番用のURLが、同じサービスを出しているか
サービス事業主名の表示(推奨) 画面の中 運営しているのが誰かを、画面の中で分かるようにする
問い合わせ先(推奨) 画面の中 問い合わせのページへのリンク、公式アカウント、メールアドレスなど
トップページ エンドポイントURL エラーや文字だけの画面にしない。リダイレクトは置かないことが勧められている
外部ブラウザからも開けること エンドポイントURL SafariやChromeで開いても画面が出る
テストシナリオ [審査のための補足資料] 予約を含むサービスは必須。テスト用の店舗や枠、操作の手順
ベーシック認証の情報 [審査のための補足資料] 審査用のURLにかけた場合のみ
各ガイドラインへの対応 画面とアイコン アイコンの仕様、ランドスケープのセーフエリア、読み込み中アイコン、カスタムアクションボタン、パフォーマンス

サービス利用規約のURLは、コンソールでは任意の項目です。確かめた範囲では、認証済ミニアプリに利用規約のURLが必須だという記載は見つけられませんでした。ただし、キャンセルの決まりや当日の連絡のように予約で約束することは、どこかに書いておかないと、問い合わせのたびに説明することになります。決済や表示の決まりが関わる場合は、専門家に確かめてください。

LINEミニアプリポリシー(最終改定日 2026年10月14日)では、認証済ミニアプリを使えるのは、日本の法人番号のある組織と、日本の個人事業主などとされています。個人は未認証ミニアプリまでです。主な機能はLINEミニアプリの中で提供することも決まりです。外のサイトやアプリへの一時的な誘導は認められていて、例として、取引や認証、別のアプリでの決済、プライバシーポリシーや利用規約や会社のサイトの閲覧、地図アプリで店の場所を開くことが挙がっています。予約の画面を開いたらすぐ外の予約サイトへ移す作りは、この決まりに合わないおそれがあります。

審査が始まって「審査中」になると、取り消しも入力の変更もできません。審査が始まる前なら、[審査申請をキャンセルする]で取り消せます。

審査に通った後

初めて審査に通ると、ステータスは「承認済み」の後すぐに「反映済み」になります。LINEの検索に出すのは、[検索可能にする]を押したときです。押さなくても、「反映済み」から30日が過ぎると、31日目の午前9時(日本時間)に自動で検索できるようになります。

公開した後の変更は、開発用で直し、審査に通ってから[変更内容を公開する]を押して本番用に写します。こちらも「承認済み」から30日が過ぎると、31日目の午前9時に自動で公開されます。審査を通った版と、本番用のURLに置いた版が食い違わないよう、デプロイの手順と公開の操作をそろえておきます。

業種ごとに変わるのは、入力の項目とリマインドの数

7つの手順は同じでも、業種で変わるのは入力の項目と、1つの予約に送る連絡の数です。

業種 入力してもらう項目 サービスメッセージで送る連絡 公式アカウントで送るもの
ジム・スタジオ 体験か会員か、クラス、名前、電話番号 予約の確認、前日のリマインド、休講 新しいクラスの案内
整体院 初めてかどうか、希望のメニュー、名前、電話番号 予約の確認、前日のリマインド 休診日のお知らせ
結婚式場のフェア予約 希望のフェアと時間、人数、名前、電話番号、結婚式の時期 予約の確認、前日のリマインド、当日の案内 次のフェアの案内

1つの予約に送れるのは5回までなので、予約の確認、変更の確認、前日のリマインド、当日の案内のように、何回目に何を送るかを先に決めておきます。結婚式場のフェア予約では、入力の欄が多いほど答える手間が増えるので、聞く項目は、当日までに本当に要るものに絞ります。フェア予約が減ったときに、どこで離れたかを確かめる手順はフェア予約が減った原因の切り分けに書きました。

会社サイトの関連ページbundlyzeのシステム開発

やってはいけないこと

作るときに起きやすい失敗を挙げます。

  • getProfile()のユーザーIDを、そのままサーバーで信じる。 サーバーにはIDトークンを送り、検証して得たsubを使います
  • 本番用のチャネルでliff-cli serveを動かす。 エンドポイントURLが書き換わり、お客様が開けなくなります
  • LIFF IDやチャネルアクセストークンを、内部チャネルの間で使い回す。 開発用のトークンで、審査用や本番用から送らないよう書かれています
  • サービスメッセージで、次のフェアやキャンペーンを案内する。 APIを一定期間使えなくなることがあります。宣伝は公式アカウントから送ります
  • 後の連絡のために、同じLIFFのアクセストークンでサービス通知トークンを発行し直す。 返ってきた新しいトークンを保存して使います
  • チャネルシークレットを、画面のコードやgitに入れる。 ステートレスのトークンを発行するのはサーバーだけにします
  • 最初のURLを、アクセス解析にそのまま送る。 アクセストークンが含まれます。ページビューはinitの後に送ります

公開前に確かめること

審査に出す前と、公開する前に、次を確かめます。

  • LINEミニアプリチャネルが、公式アカウントのMessaging APIのチャネルと同じプロバイダーにある
  • 開発用・審査用・本番用のLIFF IDとエンドポイントURLを、表にしてある
  • それぞれの内部チャネルで、liff.init()に渡すLIFF IDが合っている
  • サーバーが、IDトークンを検証してからユーザーIDを使っている
  • 名前を使うなら、profileの同意を求めてからトークンを取っている
  • サービスメッセージのテンプレートを追加し、[使用事例]に送る場面を書いた
  • 予約の行に、最新のサービス通知トークンと残りの回数を保存している
  • チラシやリッチメニューのリンクが、本番用のパーマネントリンクになっている
  • 事業主の名前、問い合わせ先、プライバシーポリシーへのリンクが画面にある
  • SafariやChromeで開いても、トップページが出る
  • 審査のための補足資料に、予約のテストシナリオを書いた
  • 予約の画面を最初から送信の完了まで、iPhoneとAndroidの実機で通した
  • 予約の確認が、お客様の側と店の側の両方に届く(店への連絡はメールや管理画面で別に持つ)
  • シークレットとトークンがgitの履歴に残っていない

確かめられなかったこと

この記事を書くときに、ドキュメントや手元で確かめられなかった点です。

  • 本物のLINEとのやり取りのすべて。チャネルの作成、内部チャネルの画面、liff.init()、IDトークンの検証、サービスメッセージの送信は試していません。コードはnode --checkで構文を確かめただけです
  • 2025年2月12日のニュース(LIFFとLINEミニアプリのブランド統合)と、2025年9月26日のニュース(外部ブラウザでの利用)の本文。どちらもページを開けず、ほかのページに書かれた要約だけを使いました。統合の時期と、既存のLIFFアプリへの影響は分かっていません
  • IDトークンの検証に失敗したときのHTTPのステータスコード。リファレンスには、失敗するとJSONが返ることとerror_descriptionの一覧は書かれていますが、ステータスコードの記載は見つけられませんでした。コードではerrorの有無も見ています
  • 3つの内部チャネルで、チャネルIDとチャネルシークレットが別の値になるか。チャネルアクセストークンを内部チャネルごとに発行するよう書かれているので、別の値として扱う前提でコードを書いています
  • LIFF CLIのchannel addとserveが、LINEミニアプリの開発用の内部チャネルでそのまま使えるか
  • 予約のカテゴリのサービスメッセージのテンプレート名と、paramsのキー
  • エンドポイントURLの末尾に/があるときの、パーマネントリンクの作られ方(ドキュメントの例は末尾に/がない形だけ)
  • 認証済ミニアプリに、サービス利用規約のURLが要るかどうか。確かめた範囲では任意の項目でした
  • LINEを入れていない人が外部ブラウザで開いたときの、2025年10月1日以降の画面

自社でやる場合と、任せる場合

予約の画面そのものは、Webの画面を作れる人が1人いれば進められます。詰まるのはその後で、内部チャネルごとの設定とデプロイ、審査の申請と待つ日数、サービス通知トークンの保存、仕様の変更への追従が続きます。予約の決まりと、何回目に何を知らせるかを決める仕事は、どの進め方でも店でしかできません。

任せる場合、株式会社bundlyzeでは、Web・システムのご相談は内容を問わずお受けしていて、公式LINEと予約・顧客管理をつなぐ仕組みも開発しています。配信や設定の運用からの入口はLINE運用のページ、開発からの入口はシステム開発のページです。

出典(一次情報)

確認日はどれも2026年11月3日です。LINEの仕様は変わることがあるので、作り始める時点で確かめてください。

よくある質問

LINEミニアプリとLIFFアプリは、何が違いますか?

どちらもLIFFの上で動くWebアプリです。LINEミニアプリはLINEミニアプリチャネルで作り、審査に通ると認証済ミニアプリとして、サービスメッセージやホーム画面へのショートカットなどが使えます。一方で、アクションボタンを消すことと、画面サイズを選ぶことはできず、1つのチャネルに置けるWebアプリは1つです。LINE Developersのドキュメントは、LIFFとLINEミニアプリのブランド統合を予定しているとして、新しく作るならLINEミニアプリとして作ることを勧めています。

Messaging APIのチャネルに、新しくLIFFアプリを追加できますか?

できません。LIFFのリリースノートでは、2020年2月5日からMessaging APIのチャネルにLIFFアプリを追加できなくなったとされています。LIFFアプリはLINEログインのチャネルに追加するか、LINEミニアプリチャネルで作ります。公式アカウントのトークやリッチメニューからは、どちらの場合もリンクで開けます。

審査に通る前でも、お客様に使ってもらえますか?

チャネルを作った時点で、未認証ミニアプリとして開ける状態になります。ただし、予約の確認に使いたいサービスメッセージは、未認証の間は開発用の内部チャネルで試せるだけで、本番では送れません。ヘッダーには名前ではなくドメインが出ます。審査はおおむね1〜2週間とされているので、本番の連絡が要るなら、その日数を見込んで申請します。

予約の確認や前日の連絡を、サービスメッセージで送る条件は何ですか?

認証済ミニアプリであること、LINEミニアプリの上でお客様が予約などの操作をしたこと、その操作への確認や結果、リマインドであることです。1つの操作につき送れるのは5回までで、サービス通知トークンは発行から1年間有効です。値下げ、クーポン、新商品、キャンペーンのような宣伝は送れません。テンプレートはLINE Developersコンソールで選び、使う場面を書いて追加します。

手元のパソコンで作りながら、スマホのLINEで確かめるにはどうしますか?

LIFF CLIのserveコマンドを使うと、手元の開発サーバーの前にHTTPSのプロキシを立て、LIFFアプリのエンドポイントURLを書き換えられます。外から届くURLが要るときは、ngrokを使う設定もあります。エンドポイントURLが書き換わるので、ドキュメントは公開済みのLIFFアプリでは実行しないよう書いています。LINEミニアプリでは開発用の内部チャネルだけで使うのが安全です。

この記事の書き方と確認の方針