Expressミドルウェアの役割
公式パッケージ @line/bot-sdk の middleware() は、LINEのWebhookを受けるルート用のExpress互換ハンドラーです。リクエストボディを加工前の状態(raw body)で読み取り、チャネルシークレットを使って x-line-signature ヘッダーを検証(HMAC-SHA256)し、署名が一致すればJSONをパースして req.body に入れます。ハンドラーではそのまま req.body.events を使えます。検証に失敗したときはエラーを next() に渡すだけで、レスポンスを自分で返すことはありません。
署名の検証は、LINEが送ったバイト列そのものに対してしか成立しません。アプリ全体に設定した express.json() などが先にボディをパースすると、検証は失敗します。この記事のコードは2026年10月時点の最新版SDK 11.3.0(Node.js 22以上が必要)を前提にしています。バージョン11では旧来の Client クラスが削除されたため、new Client(config) や client.replyMessage(token, message) を使った古いコードはコンパイルできません。この記事では代わりに LineBotClient を使います。
| エクスポート | 役割 |
|---|---|
middleware({ channelSecret }) | raw bodyの読み取り、署名の検証、JSONを req.body にパース |
LineBotClient.fromChannelAccessToken() | 型付きのMessaging APIクライアント(応答、プッシュ、プロフィール取得など)。messagingApi.MessagingApiClient も引き続きエクスポートされています |
webhook.CallbackRequest、webhook.Event | Webhookのペイロードと各イベントの型 |
validateSignature(body, secret, signature) | 同じ検証を関数として実行。Route Handlerやサーバーレス向け |
SignatureValidationFailed、JSONParseError | ミドルウェアが next() に渡すエラー |
会話部分を作る場合は、会話フローやAI応答を扱うLINE Bot開発チュートリアルを、LINEログインやSDKを使わないAPI呼び出しはLINE API連携チュートリアルをご覧ください。
インストールと設定(Node.js 22以上)
Messaging APIチャネルが必要です。2024年9月以降、LINE Developersコンソールから直接チャネルを作ることはできません。先にLINE公式アカウントを作成し、LINE Official Account ManagerでそのアカウントのMessaging APIを有効にします(アカウント作成の手順はLINE公式アカウントの作り方で解説しています)。続いてプロジェクトを準備します。
mkdir line-webhook && cd line-webhook
npm init -y
npm pkg set type=module
npm install express @line/bot-sdk
npm install -D typescript tsx @types/express @types/node
LINE Developersコンソールでチャネルを開き、チャネルシークレット(チャネル基本設定タブ)とチャネルアクセストークン(Messaging API設定タブ)をコピーします。LINEが推奨するのは、有効期限を自分で指定できるチャネルアクセストークンv2.1です。まず動かしたいだけなら長期のチャネルアクセストークンでも構いません。
# .env(このファイルはコミットしないこと)
LINE_CHANNEL_SECRET=your-channel-secret
LINE_CHANNEL_ACCESS_TOKEN=your-channel-access-token
PORT=3000
package.json に dev スクリプトを追加します。
{
"scripts": {
"dev": "tsx watch --env-file=.env src/index.ts"
}
}
最小構成の動作例
受け取ったテキストをそのまま返すオウム返しボットの src/index.ts 全体です。@line/bot-sdk 11.3.0とExpress 5、TypeScriptのstrictモードでコンパイルできることを確認しています。
// src/index.ts
import express from "express"
import {
JSONParseError,
LineBotClient,
SignatureValidationFailed,
middleware,
webhook,
} from "@line/bot-sdk"
const channelSecret = process.env.LINE_CHANNEL_SECRET
const channelAccessToken = process.env.LINE_CHANNEL_ACCESS_TOKEN
if (!channelSecret || !channelAccessToken) {
throw new Error("Set LINE_CHANNEL_SECRET and LINE_CHANNEL_ACCESS_TOKEN")
}
const client = LineBotClient.fromChannelAccessToken({ channelAccessToken })
const app = express()
// LINEのミドルウェアはWebhookのルートに置き、JSONのボディパーサーより前に通す
app.post("/webhook", middleware({ channelSecret }), async (req, res) => {
const { events } = req.body as webhook.CallbackRequest
await Promise.all(events.map(handleEvent))
res.sendStatus(200)
})
// ほかのルートでは通常どおりJSONをパースしてよい
app.use(express.json())
async function handleEvent(event: webhook.Event) {
if (event.type !== "message" || event.message.type !== "text" || !event.replyToken) return
const { sentMessages } = await client.replyMessage({
replyToken: event.replyToken,
messages: [{ type: "text", text: `Echo: ${event.message.text}` }],
})
console.log("sent message IDs:", sentMessages.map((m) => m.id))
}
// ミドルウェアはエラーをnext()に渡すだけで、401を自分では返さない
app.use((err: unknown, _req: express.Request, res: express.Response, next: express.NextFunction) => {
if (err instanceof SignatureValidationFailed) {
res.status(401).send("Invalid signature")
return
}
if (err instanceof JSONParseError) {
res.status(400).send("Invalid JSON")
return
}
next(err)
})
const port = Number(process.env.PORT ?? 3000)
app.listen(port, () => console.log(`LINE bot listening on :${port}`))
動作のポイントは4つです。
- ミドルウェアはWebhookのルートに置き、JSONパーサーより前に通します。
express.json()はこのルートより後に登録しているので、/webhookには作用しません。 res.sendStatus(200)。 LINEはすべてのWebhookリクエストにステータスコード200を期待します。接続確認のために送られる、eventsが空配列のリクエストも同様です。- エラーハンドラー。 これがないと、署名が不正なリクエストはExpressの汎用的な500で終わります。用意しておけば401や400を返せ、ログで原因も分かります。
replyTokenのチェック。 v11の型ではreplyTokenは省略可能なので、応答する前に確認します。
テストするには、ngrokなどのトンネルでポート3000をHTTPSで公開し、Messaging API設定タブのWebhook URLに https://<あなたのドメイン>/webhook を入力して検証をクリックし、Webhookの利用をオンにしてから、アカウントにメッセージを送ります。Webhook URLはHTTPSで、一般的なブラウザが信頼する認証局が発行した証明書が必要です。自己署名証明書は使えません。また、LINE Official Account Managerのあいさつメッセージと応答メッセージ(どちらも初期状態でオン)をオフにしておかないと、ユーザーにはLINEの自動応答とボットの応答の両方が届きます。
validateSignatureの仕組み(Node.js・Python・PHP)
LINEはすべてのWebhookリクエストに署名を付けます。チャネルシークレットを鍵にリクエストボディのHMAC-SHA256を計算し、Base64でエンコードした値を x-line-signature ヘッダーで送ります(Webhookの署名を検証する)。SDKの validateSignature() はこの値を計算し直し、処理時間が一定になる方法で比較します。素のNode.jsで書くと、おおよそ次のとおりです。
import { createHmac, timingSafeEqual } from "node:crypto"
// validateSignature()がしていることを数行で
export function isValidSignature(body: string | Buffer, channelSecret: string, signature: string): boolean {
const expected = createHmac("sha256", channelSecret).update(body).digest()
const received = Buffer.from(signature, "base64")
return expected.length === received.length && timingSafeEqual(expected, received)
}
検証に失敗するときの原因は、ほぼLINEのドキュメントに挙げられている次のいずれかです。
- 検証前にボディをパースした、または文字列に戻した(
express.json()が先に動く、JSON.stringify(req.body)、JSONの整形など)。受け取ったバイト列をそのまま使ってください。 - プロキシやロードバランサー、フレームワークがボディやエンコーディングを変えた。UTF-8のまま扱います。
- 別のチャネルのシークレットを使っている、またはコンソールでチャネルシークレットが再発行された。
- 環境変数の末尾に改行や空白が入っている。
- HMAC-SHA256ではなくHMAC-SHA1など、別のアルゴリズムを使っている。
署名の検証の代わりにIPアドレスで制限するのは避けてください。LINEはWebhookの送信元IPアドレスを公開しておらず、署名の検証を推奨しています。
Expressを使わない場合:Next.jsのRoute Handlerとサーバーレス
Expressのミドルウェアチェーンがない環境では、リクエストの生のテキストに対して自分で validateSignature() を呼びます。
// app/api/line/webhook/route.ts (Next.js App Router)
import { LineBotClient, validateSignature, webhook } from "@line/bot-sdk"
const channelSecret = process.env.LINE_CHANNEL_SECRET ?? ""
const client = LineBotClient.fromChannelAccessToken({
channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN ?? "",
})
export async function POST(request: Request) {
const body = await request.text() // raw body: JSON.parseより前に検証する
const signature = request.headers.get("x-line-signature") ?? ""
if (!validateSignature(body, channelSecret, signature)) {
return new Response("Invalid signature", { status: 401 })
}
const { events } = JSON.parse(body) as webhook.CallbackRequest
await Promise.all(
events.map(async (event) => {
if (event.type !== "message" || event.message.type !== "text" || !event.replyToken) return
await client.replyMessage({
replyToken: event.replyToken,
messages: [{ type: "text", text: `Echo: ${event.message.text}` }],
})
}),
)
return new Response("OK")
}
Google Cloud FunctionsやFirebaseのように、コードが動く前にボディをパースする環境もあります。ミドルウェアは req.rawBody があればそれを使います。パース済みのオブジェクトしか手元にない場合、文字列に戻すとバイト列が変わるため、署名を確実に検証することはできません。
Python(line-bot-sdk v3)
Python版SDK(3.26.0、Python 3.10以上)では、WebhookHandler.handle() の中で同じ検証が行われます。linebot.v3 のモジュールを使ってください。旧来の from linebot import LineBotApi, WebhookHandler は非推奨です。
import os
from flask import Flask, abort, request
from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.messaging import (
ApiClient,
Configuration,
MessagingApi,
ReplyMessageRequest,
TextMessage,
)
from linebot.v3.webhooks import MessageEvent, TextMessageContent
app = Flask(__name__)
configuration = Configuration(access_token=os.environ["LINE_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["LINE_CHANNEL_SECRET"])
@app.post("/webhook")
def webhook():
signature = request.headers.get("X-Line-Signature", "")
body = request.get_data(as_text=True) # request.jsonではなくraw bodyを使う
try:
handler.handle(body, signature) # 署名を検証してからハンドラーに振り分ける
except InvalidSignatureError:
abort(400)
return "OK"
@handler.add(MessageEvent, message=TextMessageContent)
def handle_text(event):
with ApiClient(configuration) as api_client:
MessagingApi(api_client).reply_message(
ReplyMessageRequest(
reply_token=event.reply_token,
messages=[TextMessage(text=f"Echo: {event.message.text}")],
)
)
検証だけを行いたい場合は、linebot.v3 の SignatureValidator(channel_secret).validate(body, signature) が True か False を返します。
PHP(line-bot-sdk-php)
現行のPHP版SDK(v12、PHP 8.2以上)では、LINE\Parser\SignatureValidator の静的メソッドです。true か false を返し、署名が空のときは InvalidSignatureException を投げます。
<?php
require __DIR__ . '/vendor/autoload.php';
use LINE\Parser\Exception\InvalidSignatureException;
use LINE\Parser\SignatureValidator;
$body = file_get_contents('php://input'); // raw request body
$signature = $_SERVER['HTTP_X_LINE_SIGNATURE'] ?? '';
$channelSecret = (string) getenv('LINE_CHANNEL_SECRET');
try {
$isValid = SignatureValidator::validateSignature($body, $channelSecret, $signature);
} catch (InvalidSignatureException $e) {
$isValid = false; // 署名ヘッダーが空
}
if (!$isValid) {
http_response_code(400);
exit;
}
// 署名OK: $bodyをデコードしてイベントを処理する
以前のリリースには \LINE\LINEBot クラスに $bot->validateSignature($body, $signature) があったため、今もこの書き方の解説記事が多く残っています。このクラスは現行リリースにはありません。検証とイベントのパースを一度に行うなら EventRequestParser::parseEventRequest($body, $channelSecret, $signature) を使います。
replyMessage(replyToken)の実装例
応答できるWebhookイベントには replyToken(応答トークン)が含まれます。Messaging APIリファレンスが定めるルールは次のとおりです。
- 応答トークンは1回だけ使えます。
- Webhookを受信してから1分以内に使います。それ以降の利用は保証されず、この時間は予告なく変わる可能性があるため、できるだけ早く応答します。
- 1回の応答で送れるメッセージオブジェクトは最大5件です。
- 応答メッセージは月間のメッセージ通数に含まれません。プッシュ、マルチキャスト、ブロードキャスト、ナローキャストのメッセージは含まれます。
テキストメッセージとFlex Messageのバブルを応答する例です。
import { LineBotClient, webhook } from "@line/bot-sdk"
const client = LineBotClient.fromChannelAccessToken({ channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN ?? "" })
export async function confirmBooking(event: webhook.MessageEvent) {
if (!event.replyToken) return
const { sentMessages } = await client.replyMessage({
replyToken: event.replyToken,
messages: [
{ type: "text", text: "ありがとうございます。ご予約を承りました。" },
{
type: "flex",
altText: "ご予約確定:土曜日 14:00",
contents: {
type: "bubble",
body: {
type: "box",
layout: "vertical",
contents: [
{ type: "text", text: "ご予約確定", weight: "bold", size: "lg" },
{ type: "text", text: "土曜日 14:00", color: "#666666", wrap: true },
],
},
footer: {
type: "box",
layout: "vertical",
contents: [
{
type: "button",
style: "primary",
action: { type: "uri", label: "予約を確認", uri: "https://example.com/bookings/123" },
},
],
},
},
},
],
notificationDisabled: false, // true = プッシュ通知なしで届ける
})
return sentMessages
}
notificationDisabled: true にすると、メッセージはプッシュ通知なしで届きます。6件以上送りたい場合や1分を過ぎてから送る場合は、残りを pushMessage で送ります(こちらは通数にカウントされます)。AIの呼び出しなど時間のかかる処理では、client.showLoadingAnimation({ chatId: userId, loadingSeconds: 20 }) で1対1のトークにローディングのアニメーションを5〜60秒表示できます。
ReplyMessageResponse.sentMessagesと引用トークン
replyMessage() は ReplyMessageResponse を返します。sentMessages 配列には送信したメッセージごとに1件ずつ、messages 配列と同じ順番で要素が入ります。
type ReplyMessageResponse = {
sentMessages: Array<{
id: string // 送信したメッセージのID
quoteToken?: string // 引用できるメッセージタイプの場合のみ
}>
}
pushMessage() も同じ形を返します。quoteToken(引用トークン)が含まれるのは、引用できるメッセージだけです。テキスト(v1とv2)、スタンプ、画像、動画、テンプレートメッセージ、Flex Message(最後の2つは引用部分に altText だけが表示されます)が該当します。引用トークンに有効期限はなく繰り返し使えますが、そのメッセージを送ったトークの中でしか使えません(引用トークンを取得する)。
引用するには、テキストメッセージに quoteToken を指定します。ユーザーから届くテキストメッセージにも quoteToken が付いているので、ボットはいまユーザーの発言を引用し、後で自分の確認メッセージを引用する、といった使い方ができます。
import { randomUUID } from "node:crypto"
import { LineBotClient, webhook } from "@line/bot-sdk"
const client = LineBotClient.fromChannelAccessToken({ channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN ?? "" })
export async function confirmOrder(event: webhook.MessageEvent, userId: string) {
if (event.message.type !== "text" || !event.replyToken) return
// 応答でユーザーのメッセージを引用する
const { sentMessages } = await client.replyMessage({
replyToken: event.replyToken,
messages: [{ type: "text", text: "ご注文 #1042 を承りました。", quoteToken: event.message.quoteToken }],
})
const orderQuoteToken = sentMessages[0]?.quoteToken // 注文データと一緒に保存しておく
// 後で(例:発送時)、確認メッセージを引用したプッシュメッセージを送る
await client.pushMessage(
{ to: userId, messages: [{ type: "text", text: "ご注文の商品を発送しました。", quoteToken: orderQuoteToken }] },
randomUUID(), // X-Line-Retry-Key: 同じキーで再試行しても二重送信されない
)
}
pushMessage() の第2引数は X-Line-Retry-Key です。同じキーのリクエストがすでに受け付けられている場合、LINEはメッセージを再送せずに409を返すので、タイムアウト後の再試行も安全に行えます。
実際に遭遇するエラー
| 症状 | 原因 | 対処 |
|---|---|---|
| すべてのWebhookが500になり、コンソールの検証も失敗する | SignatureValidationFailed がExpressの既定のエラーハンドラーまで届いている | 最小構成の例にあるエラーハンドラーを追加し、原因を直す |
| 自分のエラーハンドラーが401を返す | middleware() より前にボディがパースされている、チャネルシークレットの誤りや再発行、プロキシによるボディの変更 | /webhook で middleware() を最初に通し、シークレットを確認し、raw bodyを保つ |
| 自分のエラーハンドラーが400を返す | JSONParseError:ボディが正しいJSONではない | 手作りのテストリクエストで起きることが多い。LINEからの実際のWebhookは常にJSON |
APIが400 Invalid reply token | トークンが使用済み、期限切れ、または別チャネルのもの | 応答は1回だけ素早く。遅れて送るメッセージはプッシュで |
APIが400 The request body has X error(s) | メッセージオブジェクトが不正(6件以上、空のテキスト、Flexのプロパティの誤りなど)。details[].property に該当フィールドが入る | そのフィールドを直す。「メッセージオブジェクトを検証する」系のエンドポイントで事前に確認できる |
| APIが401 | チャネルアクセストークンがない、期限切れ、または取り消し済み | トークンを発行し直す |
| APIが429 | レート制限の超過、または You have reached your monthly limit.(無料メッセージや追加メッセージの上限に到達) | 送信ペースを落とし、プランの通数を確認する |
| APIが413 | リクエストが2MBを超えている | メッセージの数やサイズを減らす |
API呼び出しが失敗すると、status、statusText、headers、body を持つ HTTPFetchError が投げられます。
import { HTTPFetchError, LineBotClient } from "@line/bot-sdk"
const client = LineBotClient.fromChannelAccessToken({ channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN ?? "" })
export async function safeReply(replyToken: string, text: string) {
try {
await client.replyMessage({ replyToken, messages: [{ type: "text", text }] })
} catch (err) {
if (err instanceof HTTPFetchError) {
// 例: 400 {"message":"Invalid reply token"} や 429 {"message":"You have reached your monthly limit."}
console.error("LINE API error", err.status, err.body)
return
}
throw err
}
}
よく見かける説明の訂正です。LINEは、失敗したWebhookを初期設定では再送しません。Webhookの再送はMessaging API設定タブで自分で有効にするオプションです。有効にすると、2xxが返らなかったWebhookをLINEが再送します。回数と間隔は公開されていません(メッセージ(Webhook)を受信する)。
本番運用のWebhook構成
ユーザーがメッセージを送信
│
▼
LINEプラットフォーム ── HTTPS POST + x-line-signature ──▶ POST /webhook(あなたのサーバー)
│ middleware(): raw body → HMAC検証 → JSON
│ すぐに200 OKを返す
▼
handleEvent(event)
│ 独自のロジック、データベース、AI
▼
LINEプラットフォーム ◀── replyMessage / pushMessage ───────┘
│
▼
ユーザーに応答が届くLINEは、遅いリクエストが後続のイベント処理を待たせないよう、イベントを非同期で処理することを推奨しています。よく使われるのは、先に200を返してからバックグラウンドでイベントを処理し、webhookEventId で重複をスキップする形です。
const seen = new Set<string>() // 本番ではRedisやデータベースに保存する
app.post("/webhook", middleware({ channelSecret }), (req, res) => {
const { events } = req.body as webhook.CallbackRequest
res.sendStatus(200) // 先に受領を返す:LINEが必要とするのは200だけ
for (const event of events) {
if (seen.has(event.webhookEventId)) continue // 重複または再送されたイベント
seen.add(event.webhookEventId)
handleEvent(event).catch((err) => console.error("event failed", event.webhookEventId, err))
}
})
注意点は次のとおりです。
- 重複は起こります。 ネットワークの問題や再送によって、同じイベントが複数回届くことがあります。
webhookEventIdは変わらず、再送かどうかはdeliveryContext.isRedeliveryで分かります。複数のインスタンスで動かすなら、処理済みのIDはメモリではなくRedisやデータベースに保存します。 - 先に200を返すことには代償があります。 200を返したイベントはLINEから再送されないので、失敗はログに残して自分で再試行します。
- サーバーレスはレスポンス後に止まることがあります。 VercelやAWS Lambdaなどでは、レスポンスを返した後の処理が最後まで実行されない場合があります。応答トークンの1分以内に処理を終えてからレスポンスを返すか、Next.jsの
after()のようなレスポンス後に実行するAPIやキューに処理を渡します。 - Webhook URLは1チャネルに1つです。 Messaging APIチャネルはすべてのイベントを1つのURLに送ります。複数のサービスを同じチャネルにつなぐのではなく、1つのサービスの中で
event.typeごとに振り分けます。 - 状態はアプリ側で持ちます。 LINEは会話の状態を保存しません。ユーザーIDにひも付けて保存します。常に表示しておくナビゲーションにはリッチメニューを使います。
LineBot.proの使いどころ
LineBot.proはSDKでもAPIでもホスティング基盤でもなく、公開APIもありません。それでも、上のコードで開発するときに役立つ部分が2つあります。
- メッセージとメニューのデザイン。 AIジェネレーターで、リッチメッセージ(ヒーロー画像、テキスト、ボタンを持つFlexのバブル1つ)やリッチメニュー(画像とJSON、ボタン1〜6個)を作れます。JSONをコピーするか画像と一緒にダウンロードし、
replyMessageやpushMessageで送ったり、メニューを自分でアップロードしたりできます。リッチメッセージは8クレジット、リッチメニューは10クレジットです。新規登録で50クレジットが無料で付き、追加分は買い切りのパックで購入できます(月額料金はありません。料金)。リッチメッセージを作ってみる - コードを書かずに済ませる。 キーワードへの応答だけで十分なら、LINEキーワード応答ボットが、承認済みのリッチメッセージのカードで1対1のトークに応答します。どのキーワードにも一致しないメッセージは、オプションのAI振り分けでカードを選べます。AIはカードを選ぶだけで、回答文を書くことはありません。このボットはLineBot.proのWebhook URLをチャネルに設定して動くため、同じチャネルで自前のWebhookサーバーと併用することはできません。
予約システムやCRM、LIFFアプリとの連携が必要な場合は、LINE開発の受託も承っています。
よくある質問
Q: Expressでは@line/bot-sdkのミドルウェアをどこに置けばよいですか?
Webhookのルートに、どのボディパーサーよりも前に置きます。app.post("/webhook", middleware({ channelSecret }), handler) のように書き、express.json() はこのルートの後に登録するか、必要なルートだけに設定します。アプリ全体のパーサーが先に動くとraw bodyが失われ、すべてのリクエストが SignatureValidationFailed で失敗します。
Q: LINEのWebhookが401ではなく500を返すのはなぜですか?
ミドルウェアは検証に失敗してもレスポンスを返さず、next(err) を呼ぶだけです。エラーハンドラーがないと、Expressはそれを500にします。SignatureValidationFailed には401、JSONParseError には400を返すハンドラーを追加してください。
Q: line-bot-sdk-phpで署名を検証するにはどうすればよいですか?
php://input から読んだraw bodyで LINE\Parser\SignatureValidator::validateSignature($body, $channelSecret, $signature) を呼びます。戻り値は真偽値で、署名が空なら InvalidSignatureException が投げられます。\LINE\LINEBot クラスの $bot->validateSignature() は古いリリースのメソッドです。
Q: Pythonで署名を検証するにはどうすればよいですか?
line-bot-sdk v3では、WebhookHandler(channel_secret).handle(body, signature) が検証とイベントの振り分けを行い、一致しないときは InvalidSignatureError を投げます。検証だけなら linebot.v3 の SignatureValidator(channel_secret).validate(body, signature) を使います。渡すのはraw bodyで、Flaskなら request.get_data(as_text=True) です。
Q: ReplyMessageResponse.sentMessagesには何が入っていますか?
送信したメッセージごとに1つのオブジェクトがリクエストと同じ順番で入り、メッセージの id と、引用できるメッセージタイプなら quoteToken を持ちます。後のメッセージで引用したい場合は引用トークンを保存しておきます。
Q: replyMessageとpushMessageは併用できますか?
できます。まず応答トークンで応答し(無料、およそ1分以内、最大5件)、その後のメッセージは pushMessage で送ります。プッシュメッセージはプランの月間通数にカウントされ、受信者1人につき1通と数えます。
次のステップ:
- ボット全体を作る:LINE Bot開発チュートリアル(Node.js・Python)
- SDKなしでAPIを呼び、LINEログインを組み込む:LINE API連携チュートリアル
- 開発者向けの変更点をまとめて確認:LINE Messaging API 2026年のアップデート
- 開発を任せたい場合:LINE開発の受託
- AIでFlex Messageをデザインする:リッチメッセージ作成ツール



