IT技術ブログ

LINE Messaging APIの始め方予約受付と詰まる所

公式LINEに予約受付を足すためのMessaging APIの始め方です。チャネルとトークンの種類、Webhookと署名の検証、応答とプッシュの違い、通数の数え方、リッチメニュー、よくあるエラーをNode.jsの最小コードでまとめました。

この記事の結論:LINE Messaging APIは、公式アカウントに届いた操作を自社のサーバーでWebhookとして受け、APIで返事を送る仕組みです。始める順番は、公式アカウント側からチャネルを作る、チャネルアクセストークンを選ぶ、署名を確かめて先に200を返す受け口を作る、応答とプッシュを使い分ける、の4つです。詰まりやすいのは、プロバイダーを後から変えられないこと、Webhookは2秒以内に返す前提で作ること、応答トークンが1回かぎりなこと、リッチメニューのボタンをサーバーで受けるならAPIで作ることです。この記事のNode.jsのコードは手元で動かしたもので、本物のLINEでは確かめていません。

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

この記事は、小さなジム、整体院、結婚式場、リフォーム会社で、自社の公式LINEに予約受付などを足したい担当者と、その開発を担当する人に向けて書きました。「LINE公式アカウントのAPI」を探している場合、メッセージの送受信に使うのがMessaging APIです。扱うのは、最初の1通を返すまでの流れと、そこで詰まる所です。そもそも作るか、拡張ツールで足りるかを迷っている段階なら、会社のコラム拡張ツールで足りるか、自社で作るかが先です。

載せたコードは、2026年11月1日に手元のNode.jsで動かしたものです。LINEのAPIの代わりに、応答を真似る小さなサーバーを立てました。本物のLINEのチャネルは作っておらず、本物のLINEには一度も送っていません。 スマホの画面でも見ていません。コンソールの操作と仕様は、LINE Developersのドキュメントで確かめた内容です。確かめたことと確かめていないことは、後半の「動かして確かめた範囲」に分けて書きます。

Messaging APIは、Webhookで受けてAPIで返す仕組み

Messaging APIの動きは、受ける向きと送る向きの2本でできています。相手がトークで何かすると、LINEのプラットフォームが自社のサーバーへWebhookを送ります。自社のサーバーは、そのイベントを読んでAPIを呼び、返事を送ります。

予約受付に当てはめると、1回のやり取りは次のようになります。

相手の操作 サーバーに届くイベント サーバーがすること 送り方
友だち追加 follow 空いている時間のボタンを見せる 応答
「予約」と送る message 空いている時間のボタンを見せる 応答
時間のボタンを押す postback 枠を確保し、受け付けたことを返す 応答
操作なし(予約の前日) なし 予約の記録を見て連絡する プッシュ

決まった言葉に決まった答えを返すだけなら、Messaging APIは要りません。線の引き方はLINE自動返信の記事に書きました。相手や予約の状態で返事が変わるなら、この記事の範囲です。

手順1. チャネルは公式アカウント側から作り、プロバイダーを先に決める

Messaging APIのチャネルは、LINE公式アカウントでMessaging APIを有効にすると作られます。LINE Developersのドキュメントには、コンソールから直接作ることは2024年9月4日で廃止されたと書かれています。作業の順番はLINE予約システムの記事の手順1にまとめたので、ここでは先に決めることだけを書きます。

いちばん大事なのはプロバイダーです。一度選ぶと、別のプロバイダーへ変えることも、Messaging APIとの連携を解除することもできません。ドキュメントは、次の場合は後で問題が起きる可能性があるとして、慎重に選ぶよう求めています。

  • チャネルとプロバイダーを、別々の個人や企業が管理している
  • 関係のないサービスのチャネルを、同じプロバイダーの下に作る
  • 運用ツールを提供する企業のプロバイダーの下にチャネルを作る

ユーザーIDはプロバイダーごとに違う値になるので、あとでLINEログインを足す予定があるなら、同じプロバイダーを選びます。既存のプロバイダーを選ぶにはAdmin権限が要り、権限のないプロバイダーは選択の画面に出ません。

もう1つ、Webhook URLに設定できる送り先は、1つのチャネルに1つだけです。公式アカウントで別のツールをすでに使っている場合は、Webhook URLが何に使われているかを先に確かめます。

手順2. チャネルアクセストークンは、4種類から選ぶ

サーバーからAPIを呼ぶときに必要なのがチャネルアクセストークンです。Webhookの署名の検証に使うチャネルシークレットとは別の値で、シークレットは「チャネル基本設定」タブで確認できます。

トークンは4種類あります。

種類 有効期間 発行できる数(1チャネル) 発行の仕方 向いている使い方
v2.1(有効期間を指定できる) 最大30日で指定 30件。超えると発行が拒否される JWTを作って発行 LINEが勧める形
ステートレス 15分 制限なし APIで発行 使うたびに発行する。発行後は取り消せない
短期 30日 30件。超えると古いものから取り消される APIで発行 期限の前に自動で発行し直す
長期 無期限 1件 コンソールの「Messaging API設定」タブ まず動きを見る

LINE Developersのドキュメントが勧めているのはv2.1です。始める順番としては、手元の確認は長期のトークンで進め、運用に入る前にv2.1へ移す形が手軽です。これは経験則で、公式の手順ではありません。

長期のトークンは、再発行すると今のトークンが無効になります。動いているサーバーで再発行するときは、今のトークンの有効期間を最大24時間延ばす選択があるので、その間に入れ替えます。長期のトークンを使う場合は、APIを呼べるサーバーをIPアドレスで絞る設定(任意)もあります。

v2.1の発行には、RSAの鍵の組を作って公開鍵をコンソールに登録し、秘密鍵で署名したJWTを送る手順が要ります。ドキュメントは、v2.1と短期のトークンを使うたびに発行し直さず、有効期間の中では同じものを使うよう求めています。この記事のコードでは、v2.1の発行は試していません。

値はコードに書かず、gitに入れないファイルか、本番の環境変数に置きます。

.env(gitに入れない。値は各自のものを入れる)
LINE_CHANNEL_SECRET=
LINE_CHANNEL_ACCESS_TOKEN=

手順3. Webhookは、署名を確かめてから先に200を返す

受け口のコードは次の1ファイルです。Node.jsの標準機能だけで書いたので、追加のパッケージは要りません。予約の枠は説明のためにメモリに置いています。

server.mjsjs
// server.mjs:Webhook を受けて、ボタンで予約を受ける最小のサーバー
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const API = process.env.LINE_API_BASE ?? "https://api.line.me";
const SECRET = process.env.LINE_CHANNEL_SECRET;
const TOKEN = process.env.LINE_CHANNEL_ACCESS_TOKEN;
if (!SECRET || !TOKEN) throw new Error("LINE_CHANNEL_SECRET と LINE_CHANNEL_ACCESS_TOKEN を入れてください");

// 題材の枠と処理済みのID。動きを見るだけなのでメモリに置く
const slots = [
  { id: "1", label: "11/05 10:00", left: 1 },
  { id: "2", label: "11/05 11:00", left: 3 },
];
const seen = new Set();

// 受け取ったバイト列のまま、署名を確かめる
function verify(rawBody, signature) {
  const expected = createHmac("sha256", SECRET).update(rawBody).digest();
  const given = Buffer.from(signature ?? "", "base64");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

async function reply(event, messages) {
  const res = await fetch(`${API}/v2/bot/message/reply`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${TOKEN}` },
    body: JSON.stringify({ replyToken: event.replyToken, messages }),
    signal: AbortSignal.timeout(10_000),
  });
  if (res.ok) return;
  // 問い合わせに使うので、ステータス、リクエストID、本文を残す
  throw new Error(`reply ${res.status} ${res.headers.get("x-line-request-id")} ${await res.text()}`);
}

const slotButtons = () =>
  slots
    .filter((s) => s.left > 0)
    .slice(0, 13) // クイックリプライは13個まで
    .map((s) => ({
      type: "action",
      action: { type: "postback", label: s.label, data: `action=book&slot=${s.id}`, displayText: s.label },
    }));

function chooseSlot(event) {
  const items = slotButtons();
  if (items.length === 0) return reply(event, [{ type: "text", text: "いま予約できる枠がありません。" }]);
  return reply(event, [{ type: "text", text: "時間を選んでください。", quickReply: { items } }]);
}

async function handleEvent(event) {
  if (seen.has(event.webhookEventId)) return; // 同じイベントは1回だけ処理する
  seen.add(event.webhookEventId);
  if (!event.replyToken) return; // 友だち解除や standby には応答トークンがない

  if (event.type === "follow") return chooseSlot(event);
  if (event.type === "message" && event.message.type === "text" && event.message.text.includes("予約")) {
    return chooseSlot(event);
  }
  if (event.type !== "postback") return;

  const p = new URLSearchParams(event.postback.data);
  if (p.get("action") === "dates") return chooseSlot(event);
  if (p.get("action") === "book") {
    const slot = slots.find((s) => s.id === p.get("slot"));
    if (!slot || slot.left <= 0) return chooseSlot(event); // 見せた後に埋まった枠
    slot.left -= 1;
    return reply(event, [{ type: "text", text: `${slot.label} のご予約を受け付けました。` }]);
  }
}

createServer(async (req, res) => {
  if (req.method !== "POST" || req.url !== "/webhook") return res.writeHead(404).end();

  const chunks = [];
  try {
    for await (const chunk of req) chunks.push(chunk);
  } catch {
    return res.destroy(); // 本文の途中で切れた接続。拾わないとサーバーごと落ちる
  }
  const rawBody = Buffer.concat(chunks);

  if (!verify(rawBody, req.headers["x-line-signature"])) return res.writeHead(401).end();
  res.writeHead(200).end(); // 署名が合ったら先に200を返し、処理はその後

  const { events = [] } = JSON.parse(rawBody); // 空の配列なら何もしない
  for (const event of events) {
    await handleEvent(event).catch((err) => console.error(event.webhookEventId, err.message));
  }
}).listen(process.env.PORT ?? 3000);

動かすコマンドは次のとおりです。--env-fileはNode.jsの機能で、.envの値を環境変数として読み込みます。

bash
node --env-file=.env server.mjs

コードの要点は4つです。詳しい理由はWebhookの記事に書いたので、ここではNode.jsで書くときの差を中心に挙げます。

  • 本文はバイト列のまま検証する。 署名はチャネルシークレットを鍵にしたHMAC-SHA256です。LINEのドキュメントは、検証の前のJSONの読み込み、整形、エスケープの解釈、UTF-8以外での処理を禁じています。Expressなどの本文を自動で読み込む道具を使う場合は、読み込む前の本文を取っておきます
  • 2秒より前に返す。 ドキュメントの「エラーの統計情報」の説明では、2秒以内にレスポンスが返らないとrequest_timeoutになるとされています。予約の処理を終えてから返すのではなく、署名が合ったら先に200を返します
  • 空のeventsにも200を返す。 疎通の確認のために、イベントのないリクエストが届くことがあります
  • ヘッダー名の大文字と小文字に頼らない。 ヘッダー名の表記は予告なく変わることがあるとされています。Node.jsのreq.headersは小文字にそろえてくれるので、x-line-signatureで読めます

Workersでは先に200を返すのにctx.waitUntil()が要りましたが、ずっと動いているNode.jsのサーバーでは、返した後の処理をそのまま続けられます。その代わり、処理の途中でサーバーが止まると、そのイベントは失われます。LINEには成功と伝わっているので、再送もされません。

サーバーを公開したら、コンソールの設定に進みます。

  1. 「Messaging API設定」タブの「Webhook URL」に、https://(公開したドメイン)/webhookを入れる
  2. 「検証」を押して「成功」と出ることを確かめる
  3. 「Webhookの利用」を有効にする
  4. 同じタブのQRコードで、公式アカウントを友だち追加する
  5. LINE Official Account Managerで、あいさつメッセージと応答メッセージの扱いを決める

Webhook URLはHTTPSが必須で、自己署名の証明書は使えません。手元のサーバーをそのまま登録することはできないので、公開の環境に置くか、HTTPSのURLを手元へ転送する道具を使います。

チャネルを作った直後は、あいさつメッセージと応答メッセージが有効です。Webhookと一緒に有効のままにすると、管理画面の返事とサーバーの返事が重なることがあります。ドキュメントは、初めてボットを作るときはオフにするよう勧めています。

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

手順4. 応答とプッシュは、きっかけで使い分ける

Messaging APIの送り方を分ける基準は、相手の操作に返すのか、こちらの都合で送るのかです。

応答メッセージ プッシュメッセージ
きっかけ 友だち追加、メッセージ、ボタンの操作などのイベント いつでも
宛先の指定 応答トークン(replyToken) ユーザーID(to)
料金プランの通数 数えられない 送信の対象の人数で数えられる
時間の制限 応答トークンは1回かぎり。受信から1分以内 なし
送り直し X-Line-Retry-Keyは使えない X-Line-Retry-Keyで二重送信を防げる
予約受付での例 枠のボタン、受け付けの確認 前日のリマインド、店からの変更の連絡

1回で送れる吹き出しは、どちらも5つまでです。

応答トークンには細かい決まりがあります。リファレンスでは、1回しか使えず、Webhookの受信から1分以内に使う必要があり、再送されたWebhookの応答トークンは、元のトークンを使っていたり、イベントの発生から20分を過ぎていたりすると使えないとされています。時間の制限は予告なく変わることがあるので、制限に頼らず、できるだけ早く使います。

返事に時間がかかる処理なら、トーク画面にローディングのアニメーションを出すAPIがあります。表示は5〜60秒で指定し、1対1のトークだけで使えて、相手がそのトーク画面を見ているときにだけ出ます。

プッシュは、次の短いスクリプトで試せます。送り先には、「チャネル基本設定」タブに出る開発者本人のユーザーIDを使います。LINEアカウントと連携していないビジネスIDでは表示されません。

なお、相手にブロックされている場合、プッシュは 200 が返っても届きません。送信の成功と到着は別のものとして扱います。

push.mjsjs
// push.mjs:自分のユーザーIDに1通送る。node --env-file=.env push.mjs <ユーザーID> <文>
import { randomUUID } from "node:crypto";

const API = process.env.LINE_API_BASE ?? "https://api.line.me";
const [to, text] = process.argv.slice(2);

const res = await fetch(`${API}/v2/bot/message/push`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.LINE_CHANNEL_ACCESS_TOKEN}`,
    "X-Line-Retry-Key": process.env.RETRY_KEY ?? randomUUID(), // 送り直すときは同じ値を使う
  },
  body: JSON.stringify({ to, messages: [{ type: "text", text }] }),
});
console.log(res.status, res.headers.get("x-line-request-id"), await res.text());

X-Line-Retry-Keyには、送るたびに新しいUUIDを付けます。タイムアウトなどで届いたか分からないときは、同じ値で送り直します。すでに受理されていれば409が返り、二重には届きません。リトライキーは最初のリクエストから24時間有効です。自社の連絡の形にするときの作りは、前日リマインドの記事に書きました。

メッセージ通数は、送った相手の人数で数える

メッセージ通数は、吹き出しの数でもリクエストの回数でもなく、送信の対象になった人数で数えます。LINE Developersのドキュメントの例では、メッセージを4件入れたプッシュを5人のトークルームに1回送ると、5通です。

送り方 通数 予約受付での例
応答 数えられない 枠のボタン、受け付けの確認
プッシュ 数えられる 前日のリマインド
マルチキャスト 数えられる 指定した人たちへの休業の連絡
ナローキャスト 数えられる 属性やオーディエンスで絞った案内
ブロードキャスト 数えられる 友だち全員への配信

ブロックされている相手や存在しないユーザーIDのように、実際には届かない宛先は数えられません。

見積もるときは、相手の操作への返事を除いて、こちらから送る分だけを足します。

月の通数 = 月の予約の件数 × 予約1件あたりのプッシュの回数 + 友だちの数 × 月の配信の回数

たとえば前日のリマインドだけなら、予約1件につき1通です。すべての連絡をプッシュで作ると、同じやり取りでも通数が増えます。相手の操作をきっかけにできる連絡は、応答で返す形に寄せます。

上限を超えて送ろうとすると、エラーが返り、メッセージは送られません。当月の状況はAPIで確かめられます。

quota.mjsjs
// quota.mjs:今月の上限の目安と、使った通数を見る
const API = process.env.LINE_API_BASE ?? "https://api.line.me";
const get = async (path) => {
  const res = await fetch(`${API}${path}`, {
    headers: { Authorization: `Bearer ${process.env.LINE_CHANNEL_ACCESS_TOKEN}` },
  });
  return [res.status, await res.json()];
};
console.log("上限の目安", ...(await get("/v2/bot/message/quota")));
console.log("使った通数", ...(await get("/v2/bot/message/quota/consumption")));

上限の目安は、typeがlimitedのときだけvalueが返り、noneなら目安が設定されていません。どちらの数字にも、LINE Official Account Managerから送った分が含まれます。管理画面の配信とAPIの連絡は、同じ枠を使う前提で考えます。使った通数の値は概算とされているので、正確な数はLINE Official Account Managerで見ます。プランごとの通数と金額は変わることがあるので、LINE公式アカウントの料金プランで確かめます。

リッチメニューは、ポストバックが要るならAPIで作る

リッチメニューは、LINE Official Account ManagerとMessaging APIのどちらでも作れます。予約受付で分かれ目になるのは、ボタンを押したときにサーバーへ知らせたいかどうかです。LINE Developersのドキュメントでは、ポストバックアクションや日時選択アクションを使えることが、Messaging APIで作る利点に挙げられています。

LINE Official Account Manager Messaging API
作り方 画面で設定する 定義を送り、画像を付け、既定に設定する
向いている形 予約ページなどへのリンクを並べる ボタンをサーバーで受ける、人ごとにメニューを変える
ポストバック 利点に挙げられているのはAPIのみ 使える
表示期間の設定 できる 利点には挙げられていない

予約ページが別にあり、リンクを開くだけなら、管理画面で足ります。トークの中で枠を選んでもらうなら、APIで作ります。上のserver.mjsのaction=datesは、リッチメニューのボタンにdataとして入れる値です。作るスクリプトはLINE予約システムの記事の手順3に載せたので、ここでは繰り返しません。

作る前に知っておきたいのは次の4つです。

  • 1つのメニューには1つの道具だけ。 管理画面で作ったメニューはAPIで扱えず、APIで作ったメニューは管理画面で扱えません
  • パソコン版のLINEには出ない。 リッチメニューはデスクトップ版(macOS、Windows)では表示されません。文字で「予約」と届いた場合にも同じ案内を返しておくと、メニューが見えない人の受け口になります
  • APIのメニューが優先される。 表示の順は、ユーザー単位、APIの既定、管理画面の既定です。管理画面で直したのに変わらないときは、APIの既定が残っていないかを見ます
  • 既定のメニューはすぐには変わらない。 既定のメニューは、トーク画面に入り直したときに反映されます。APIの既定は、反映まで1分ほどかかることがあります

予約の前と後でメニューを変える設計は、段階で切り替える記事で扱っています。

よくあるエラーは、ステータスとmessageで切り分ける

Messaging APIのエラーは、ステータスコードとJSONのmessageで原因を絞れます。上のserver.mjsは、失敗したときにステータス、X-Line-Request-Id、本文をログに残します。

見えるもの 意味 確かめること
APIが401 有効なトークンが指定されていない トークンの期限切れ、再発行、環境変数の入れ忘れ、Bearer の付け忘れ
400 Invalid reply token 応答トークンが期限切れか使用済み 同じイベントを2回処理していないか、待たせすぎていないか
400 The request body has X error(s) 送った内容の誤り detailsのpropertyが指す場所。吹き出しの数、ボタンの数、文字数
403 権限がない 契約中のプランとアカウントの権限
404 Not found プロフィールを取れない 友だち追加の有無、ブロック、プロフィール取得の同意
409 同じリトライキーがすでに受理されている 重複を防げた結果なので、送り直さない
429 You have reached your monthly limit. 月の通数の上限を超えた 当月の状況。配信の最中は、残っていても返ることがあるので、公式のFAQも見る
429 The API rate limit has been exceeded. Try again later. レート制限を超えた 同じ処理のくり返し。時間をおいて送る
Failed to send messages 送信に失敗した ユーザーIDの取り違え。存在しないIDには送らない

自社のサーバーの側で起きる失敗は、次のとおりです。

見えるもの 意味 確かめること
自社のサーバーが署名で401 シークレットの取り違えか、本文が変わっている シークレットの再発行、間に入るプロキシ、検証の前の読み込み
could_not_connect サーバーへ接続できない HTTPS、証明書の中間証明書、URLのホスト名
request_timeout 2秒以内に返らなかった 先に200を返しているか
error_status_code 200番台以外を返した サーバーのログ、パスの誤り
同じ相手に2通届く 管理画面の応答メッセージも動いていることがある LINE Official Account Managerの応答設定

シークレットを新しく発行すると、古い値は無効になります。ある日から突然署名が合わなくなったら、まず再発行の有無を確かめます。

could_not_connectなどの原因は、コンソールの「エラーの統計情報」で見られます。この機能は初期設定で無効で、オフの間の分はさかのぼれません。「検証」のリクエストは統計に入らないので、検証の失敗はサーバーのログで見ます。LINEのガイドラインは、送ったリクエストと受けたWebhookのログを一定期間残すよう勧めています。

最小のコードの次に足すもの

ここまでのコードは、動きを見るためのものです。枠と処理済みのIDがメモリにあるので、再起動すると消えます。実際の予約に使うときは、次の順で足します。

足したいこと 変えること 詳しい記事
枠と予約を残す データベースに移し、空きの確認と登録を1回の処理にする LINE予約システムの作り方、二重予約の防止
再送と重複に強くする webhookEventIdをデータベースで判定する Webhookの受け方
前日に連絡する 定期実行でプッシュを送り、二重送信を防ぐ 前日リマインド
自由な文に答える 文の言葉で答えを選び、人に渡す線を決める LINE自動返信
予約の台帳とつなぐ LINEの人と顧客の記録を結ぶ 公式LINE連携の進め方

業種で変わるのは、主に枠の持ち方です。ジムや整体院は時間と担当者、結婚式場は見学の時間帯やフェアの回、リフォーム会社は現地を見に行ける希望日になります。違いはLINE予約システムの記事の業種の節にまとめました。

名前や電話番号をトークの中で聞き始めると、やり取りが長くなります。入力が要るなら、予約の画面だけをLINEミニアプリで作る形も検討します。

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

やってはいけないこと

  • トークンやシークレットをコードやgitに入れる。 例の値でも、本物と同じ形の文字列を書かないようにします。漏れた疑いがあれば、LINEのドキュメントのとおり取り消して発行し直します
  • 検証の前に本文をJSONにする。 中身が同じでも署名が合わなくなります
  • WebhookをIPアドレスで絞る。 LINEのガイドラインで禁じられています。LINEのプラットフォームのIPアドレスは開示されておらず、守りは署名の検証で行います
  • 本物のLINEで負荷試験をする。 ガイドラインは、大量のリクエスト、プラットフォームを経由した負荷テスト、同じユーザーへの大量送信を禁じています。手元の試験は、この記事のように真似るサーバーで行います
  • 応答トークンを取っておいて後で使う。 後から送る連絡はプッシュにします
  • ボタンのdataをそのまま信じる。 見せた後に枠が埋まることがあり、トークに残るボタンなら古いものも押せます。枠の空きと本人を、押された時点で確かめ直します

公開前に確かめること

  • プロバイダーが、今後LINEログインを足すものと同じになっている
  • 本物のチャネルで、コンソールの「検証」が成功する
  • 運用で使うトークンの種類を決め、発行し直す手順を書いた
  • シークレットとトークンがgitの履歴に残っていない
  • 管理画面のあいさつメッセージと応答メッセージの扱いを決めた
  • 「エラーの統計情報」をオンにした
  • 友だち追加から予約の受け付けまでを、iPhoneとAndroidの実機で通した
  • リッチメニューのボタンが、意図したdataで届く
  • パソコン版のLINEから「予約」と送っても案内が返る
  • 月の通数を見積もり、当月の状況を見る人を決めた
  • 失敗のログに、リクエストIDが残っている
  • サーバーが止まったときの連絡先を決めた

動かして確かめた範囲

2026年11月1日に、手元のMacで確かめました。

項目 バージョン・内容
OS macOS 26
Node.js 24.22.1(追加のパッケージなし)
Webhook 手元で署名を計算してserver.mjsへ送った
LINEのAPI 手元の真似るサーバー。本物のLINEには送っていない

server.mjsは90行、push.mjsは16行、quota.mjsは10行です。試験の結果は次のとおりで、24項目を3回流し、3回とも全項目が通りました。

試したこと 結果
日本語、改行、絵文字を含む本文に正しい署名 200。枠のボタンを2つ返した
違う鍵、本文の書き換え、整形し直し、エスケープの書き換え、ヘッダーなし、Base64でない値 どれも401で、何も送らなかった
空のevents 200で、何も送らなかった
友だち追加、ポストバックのaction=dates 枠のボタンを返した
残り1の枠を予約し、もう一度同じ枠を押す 1回目は受付、2回目は残りの枠のボタンを返した
同じwebhookEventIdの再送 2回目は返事を送らなかった
本文の途中で切れた接続 サーバーは落ちず、次のリクエストを受けた
友だち解除、standby、スタンプ どれも返事を送らなかった
使用済みの応答トークン ログにreply 400と本文が残り、その後のイベントも処理できた
APIが3秒遅れる状態でイベントを送る Webhookは1ミリ秒ほどで200を返し、3秒後に返事を送った
push.mjs、同じリトライキーでもう1回 200、409
間違ったトークンでpush.mjs 401
quota.mjs 2つの値を表示した

エスケープの書き換えは、本文の\nを\u000aに変えたものです。JSONとしての中身は同じでも、署名が合わなくなることを確かめました。

確かめていないこと:

  • 本物のLINEとのやり取りのすべて。チャネルの作成、トークンの発行、コンソールの「検証」、実際のWebhookの形、応答とプッシュの到着、ボタンの見え方は見ていません。真似るサーバーはドキュメントを読んで書いたもので、本物と同じ動きとは限りません
  • 上のエラーの表は、ドキュメントのステータスコードとエラーメッセージから書いたものです。動かして試したのは、真似るサーバーが返す400と401と409だけです
  • 公開の環境へのデプロイと、HTTPSの設定。2秒の制限を実際のネットワークで測っていません
  • v2.1のトークンの発行と、リッチメニューの作成。リッチメニューのスクリプトは予約システムの記事のもので、この記事では動かしていません
  • ローディングのアニメーションのAPI
  • 月の通数の上限を超えたときの実際の動き。上限を超えた後に応答メッセージが送れるかは、確かめたドキュメントの範囲では読み取れませんでした
  • 大きな本文を送りつけられたときの備え。server.mjsは本文の大きさを制限していないので、本番では上限を付けます

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

最初の1通を返すところまでは、Node.jsを読める人が1人いれば進められます。詰まるのはその後で、トークンの発行し直し、失敗の見張り、仕様の変更への追従、通数の管理が続きます。予約の決まりと、自動で答えないことを決める仕事は、どの進め方でも店でしかできません。Node.jsで書かない場合も、Python、Javaなどの公式のSDKがあります。

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

出典(一次情報)

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

よくある質問

LINE Messaging APIは何に使うものですか?

LINE公式アカウントに届いたメッセージやボタンの操作を自社のサーバーで受け取り、プログラムから返事や連絡を送るためのAPIです。予約の枠を見せて受け付ける、予約の前日に連絡するといった、相手や状態で内容が変わるやり取りに使います。決まった言葉に決まった答えを返すだけなら、管理画面の応答メッセージで足ります。

チャネルアクセストークンは、どれを使えばよいですか?

LINE Developersのドキュメントは、Messaging APIではチャネルアクセストークンv2.1を勧めています。有効期間を最大30日で指定でき、JWTを作って発行します。まず動きを見るだけなら、コンソールの「Messaging API設定」タブから発行できる長期のトークンが手軽です。ただし有効期限がないので、漏れたときの影響が長く続きます。

応答メッセージとプッシュメッセージの違いは何ですか?

応答メッセージは、Webhookで受け取った応答トークンを使って、相手の操作に返す送り方です。料金プランの通数に数えられません。プッシュメッセージは、ユーザーIDを指定して好きなときに送れる送り方で、通数に数えられます。予約の確認は応答で、前日のリマインドはプッシュで送ります。

メッセージ通数はどう数えられますか?

送信の対象になった人数で数えます。1回のリクエストに吹き出しを4つ入れて5人に送ると、5通です。ブロックされている相手や存在しないユーザーIDのように、実際には届かない相手は数えられません。当月の上限の目安と使った通数は、APIで確かめられます。

Webhook URLの「検証」が失敗するときは、どこを見ればよいですか?

HTTPSで受けていること、広く信頼された認証局の証明書を使っていること(自己署名は使えません)、サーバーが200を返していることの順に確かめます。署名の検証で401を返している場合は、チャネルシークレットの値を見直します。「エラーの統計情報」には検証のリクエストは入らないので、サーバーのログを見ます。

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