LINE API 串接教學:Messaging API、Login 與 LIFF
給開發者的 2026 年 LINE API 全貌,以及不用 SDK 直接呼叫的方法:各 API 能做什麼、Channel 設定、用 fetch 回覆與推播、Webhook 簽章驗證、搭配 PKCE 的 LINE Login、LIFF 的 ID Token、速率限制與錯誤處理。

#LINE API 能做什麼(2026)
LINE 開放給開發者的主要元件有四個:以 LINE 官方帳號收發訊息的 Messaging API、讓使用者用 LINE 帳號登入你的網站或 App 的 LINE Login、在 LINE 裡執行網頁應用程式的 LIFF(LINE Front-end Framework),以及把 LIFF 應用程式上架成 LINE 內服務的 LINE MINI App。這篇教學不使用 SDK,而是在 Node.js 22 用 fetch 直接以 HTTPS 呼叫這些 API,並附上 Python 版的 Webhook。內容依據 2026 年 10 月的 LINE Developers 官方文件查證。
| API | 能做什麼 | 常見用途 |
|---|---|---|
| Messaging API | 回覆訊息;傳送推播、Multicast、Broadcast、Narrowcast 訊息;圖文選單、快速回覆、Flex Message;取得使用者個人檔案、受眾、統計資料、優惠券 | 聊天機器人、訂單與預約通知、客服 |
| LINE Login | 以 OAuth 2.0/OpenID Connect 登入;取得使用者 ID、顯示名稱與大頭貼;經 LINE 核准後可取得電子郵件;登入時邀請使用者將官方帳號加入好友 | 網站或 App 的「使用 LINE 登入」、會員帳號綁定 |
| LIFF | 在 LINE 內開啟、能辨識使用者的網頁應用程式(ID Token、個人檔案),可傳訊息到目前的聊天室、開啟分享對象選擇器、掃描 QR Code | 表單、會員卡、LINE 內的預約頁面 |
| LINE MINI App | 由 LINE 上架成服務的 LIFF 應用程式;自 2026 年 3 月 11 日起,任何人都能在台灣與泰國發布未認證的 MINI App | 不必開發原生 App 的門市應用、預約、會員服務 |
| LINE 社群外掛(Social Plugins) | 網站用的分享按鈕與加入好友按鈕 | 分享文章、從網站增加好友 |
部分 API 只開放給通過申請的企業用戶(例如 LINE 通知型訊息)。另外有兩項服務在舊教學中仍常出現,但已經結束:LINE Notify 已於 2025 年 3 月 31 日終止服務(改用 LINE 官方帳號透過 Messaging API 傳送推播訊息,可參考 LINE Notify 替代方案);LINE Pay 在日本結束服務後,LINE 也於 2025 年 4 月停止提供 LINE Pay 的開發文件。
該選哪一個?
- 回應客人或傳送通知的機器人 → Messaging API
- 自家網站或 App 的登入功能 → LINE Login
- 在 LINE 內開啟、知道使用者是誰的網頁 → LIFF(LINE 目前建議新的 LIFF 應用程式直接建立成 LINE MINI App)
- 同時需要聊天與網頁介面 → 在同一個 Provider 下搭配 Messaging API 與 LIFF
想用官方 SDK 的話,@line/bot-sdk Express 中介軟體範例與 LINE Bot 開發教學會以 @line/bot-sdk v11 實作同樣的 Webhook。
#開發前準備:Provider、Channel 與憑證
1. 開發者帳號與 Provider:登入 LINE Developers Console,建立代表提供服務的公司或個人的 Provider。請謹慎選擇:Channel 之後無法移到別的 Provider,而且同一位 LINE 使用者在不同 Provider 下的使用者 ID 也不同。需要共用使用者 ID 的 Messaging API Channel 與 LINE Login Channel,必須放在同一個 Provider 下。
2. Messaging API Channel:自 2024 年 9 月 4 日起,無法再從 Console 直接建立 Messaging API Channel。請先建立 LINE 官方帳號,再到 LINE 官方帳號管理後台的設定 → Messaging API 啟用並選擇 Provider,Channel 就會出現在 Console 中。
3. LINE Login Channel:在同一個 Provider 下從 Console 建立,選擇 App 類型(Web app 或 Mobile app),並在 LINE Login 分頁登錄 Callback URL。
4. LIFF 應用程式:LIFF 應用程式要加在 LINE Login Channel 或 LINE MINI App Channel 中(每個 Channel 最多 30 個),不能加在 Messaging API Channel。新增後會取得 LIFF ID 與 LIFF URL(https://liff.line.me/{liffId})。
Provider "My Shop"
├── Messaging API channel (與 LINE 官方帳號連動)
├── LINE Login channel (網站登入+LIFF app)
└── LINE MINI App channel (視需要)
| 憑證 | 位置 | 用途 |
|---|---|---|
| Channel ID | Basic settings 分頁 | LINE Login 的 client_id、驗證 ID Token、發行 Token |
| Channel secret | Basic settings 分頁 | 驗證 Webhook 簽章、LINE Login 換取 Token |
| Channel access token | Messaging API 分頁(長效型)或透過 API 發行 | 呼叫 Messaging API 時的 Authorization: Bearer 標頭 |
| LIFF ID | LINE Login 或 MINI App Channel 的 LIFF 分頁 | liff.init({ liffId }) |
Channel access token 共有四種:可自訂效期的 Token(v2.1,最長 30 天,LINE 建議使用)、無狀態 Token(15 分鐘)、短效型 Token(30 天),以及每個 Channel 一組的長效型 Token(不會過期,僅限 Messaging API)。所有密鑰都只放在伺服器端的環境變數或密鑰管理服務中:
# .env
LINE_CHANNEL_ACCESS_TOKEN=...
LINE_CHANNEL_SECRET=...
LINE_LOGIN_CHANNEL_ID=...
LINE_LOGIN_CHANNEL_SECRET=...
#Messaging API:用 fetch 實作回覆、推播與 Multicast
呼叫 Messaging API 就是對 https://api.line.me 發出 HTTPS 請求,並在 Authorization 標頭帶上 Channel access token。下面這個小工具函式就能涵蓋大部分機器人的傳訊需求:
// line-api.ts:用 fetch 寫的精簡 Messaging API 用戶端(Node.js 22 以上)
const API = "https://api.line.me/v2/bot"
const token = process.env.LINE_CHANNEL_ACCESS_TOKEN
if (!token) throw new Error("Set LINE_CHANNEL_ACCESS_TOKEN")
export type LineMessage = { type: string; [key: string]: unknown }
export class LineApiError extends Error {
constructor(public status: number, public requestId: string | null, public body: string) {
super(`LINE API ${status}: ${body}`)
}
}
async function post(path: string, body: unknown, headers: Record<string, string> = {}) {
const res = await fetch(`${API}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...headers },
body: JSON.stringify(body),
})
if (!res.ok) throw new LineApiError(res.status, res.headers.get("x-line-request-id"), await res.text())
return res.json() as Promise<{ sentMessages?: { id: string; quoteToken?: string }[] }>
}
// 回覆:需要 Webhook 事件的 replyToken,最多 5 則,不計入訊息則數
export function reply(replyToken: string, messages: LineMessage[]) {
return post("/message/reply", { replyToken, messages })
}
// 推播:傳給 1 位使用者、1 個群組或聊天室;重試同一次推播時沿用同一個 retryKey(UUID)
export function push(to: string, messages: LineMessage[], retryKey: string) {
return post("/message/push", { to, messages }, { "X-Line-Retry-Key": retryKey })
}
// Multicast:每次請求最多 500 個 user ID
export function multicast(to: string[], messages: LineMessage[]) {
if (to.length > 500) throw new Error("multicast accepts at most 500 user IDs")
return post("/message/multicast", { to, messages })
}
回覆還是推播? 回覆訊息要使用 Webhook 事件中的 replyToken。每個 Token 只能用一次,LINE 要求在收到 Webhook 後約 1 分鐘內使用(超過時間不保證能成功)。回覆訊息不計入每月訊息則數;推播、Multicast、Broadcast、Narrowcast 則會計入,而且是依收件人數計算,與一次請求中的訊息物件數量(最多 5 個)無關。已封鎖帳號的使用者不計入。
| 傳送方式 | Endpoint | 收件對象 | 是否計入則數 |
|---|---|---|---|
| 回覆 | POST /v2/bot/message/reply | 該事件的使用者、群組或聊天室 | 不計入 |
| 推播 | POST /v2/bot/message/push | 1 位使用者、1 個群組或聊天室 | 計入 |
| Multicast | POST /v2/bot/message/multicast | 最多 500 個使用者 ID | 計入 |
| Broadcast | POST /v2/bot/message/broadcast | 所有好友 | 計入 |
| Narrowcast | POST /v2/bot/message/narrowcast | 受眾或依屬性篩選的對象 | 計入 |
推播訊息可以傳給好友、帳號所在的群組與聊天室,以及過去 7 天內傳訊給帳號的使用者(即使不是好友)。推播因網路錯誤或 5xx 失敗而重試時,請帶上同一個 X-Line-Retry-Key:如果第一次請求已被受理,LINE 會回傳 409,而不會重複傳送。
import { randomUUID } from "node:crypto"
import { push, type LineMessage } from "./line-api.js"
const userId = "U0123456789abcdef0123456789abcdef"
const welcome: LineMessage[] = [
{
type: "text",
text: "您的訂單 #1042 已出貨。",
quickReply: {
items: [
{ type: "action", action: { type: "uri", label: "查詢物流", uri: "https://example.com/track/1042" } },
{ type: "action", action: { type: "message", label: "聯絡客服", text: "我想詢問訂單 #1042" } },
],
},
},
]
const retryKey = randomUUID()
const result = await push(userId, welcome, retryKey)
console.log(result.sentMessages?.map((m) => m.id))
訊息都是 JSON 物件,最常用的三種如下:
{ "type": "text", "text": "您好!請問需要什麼協助?" }
{
"type": "image",
"originalContentUrl": "https://example.com/photo.jpg",
"previewImageUrl": "https://example.com/photo-preview.jpg"
}
{
"type": "flex",
"altText": "週末限定:飲品 8 折",
"contents": {
"type": "bubble",
"hero": {
"type": "image",
"url": "https://example.com/promo.jpg",
"size": "full",
"aspectRatio": "20:13",
"aspectMode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{ "type": "text", "text": "週末限定優惠", "weight": "bold", "size": "xl" },
{ "type": "text", "text": "全品項飲料 8 折,到週日為止", "wrap": true }
]
},
"footer": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "button",
"style": "primary",
"action": { "type": "uri", "label": "查看菜單", "uri": "https://example.com/menu" }
}
]
}
}
}
文字訊息最多 5,000 個字元,快速回覆最多 13 個按鈕,所有圖片與連結都必須是 HTTPS。每月可免費傳送的則數依 LINE 官方帳號方案與國家而定,請參考 LINE 官方帳號費用指南;用量可透過 GET /v2/bot/message/quota 與 GET /v2/bot/message/quota/consumption 查詢。
#LINE Login:搭配 PKCE 的授權碼流程
LINE Login 遵循 OAuth 2.0 與 OpenID Connect,流程如下:
- 伺服器將使用者導向
https://access.line.me/oauth2/v2.1/authorize,並帶上state(防止跨站請求偽造)、nonce(防止重送攻擊)與 PKCE 的code_challenge。 - 使用者在 LINE 登入,並在同意畫面按下允許。
- LINE 帶著
code與state導回你的 Callback URL;使用者拒絕時則會帶error=ACCESS_DENIED。 - 伺服器以
POST https://api.line.me/oauth2/v2.1/token用授權碼換取 Token,並附上code_verifier。 - 伺服器以
POST https://api.line.me/oauth2/v2.1/verify請 LINE 驗證 ID Token,其中的sub就是使用者 ID。
// login.ts:用 Express 5 實作「使用 LINE 登入」(授權碼流程+PKCE)
import express from "express"
import { createHash, randomBytes } from "node:crypto"
const CHANNEL_ID = process.env.LINE_LOGIN_CHANNEL_ID!
const CHANNEL_SECRET = process.env.LINE_LOGIN_CHANNEL_SECRET!
const REDIRECT_URI = "https://example.com/auth/line/callback" // 必須與 Console 設定的 Callback URL 相同
const app = express()
// 示範用的暫存;正式環境請改用 session store 或資料庫
const pending = new Map<string, { nonce: string; codeVerifier: string }>()
function readCookie(header: string | undefined, name: string) {
return header?.split(/;\s*/).find((c) => c.startsWith(`${name}=`))?.slice(name.length + 1)
}
app.get("/auth/line", (_req, res) => {
const state = randomBytes(16).toString("hex")
const nonce = randomBytes(16).toString("hex")
const codeVerifier = randomBytes(32).toString("base64url") // 43 個字元
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url")
pending.set(state, { nonce, codeVerifier })
res.cookie("line_state", state, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 10 * 60 * 1000 })
const params = new URLSearchParams({
response_type: "code",
client_id: CHANNEL_ID,
redirect_uri: REDIRECT_URI,
state,
scope: "profile openid",
nonce,
code_challenge: codeChallenge,
code_challenge_method: "S256",
})
// URLSearchParams 會把空格編成 "+",LINE 文件範例使用 %20
res.redirect(`https://access.line.me/oauth2/v2.1/authorize?${params.toString().replaceAll("+", "%20")}`)
})
app.get("/auth/line/callback", async (req, res) => {
const { code, state, error } = req.query
const saved = typeof state === "string" ? pending.get(state) : undefined
if (typeof state === "string") pending.delete(state)
if (error || typeof code !== "string" || !saved || state !== readCookie(req.headers.cookie, "line_state")) {
res.status(400).send("Login cancelled or invalid state") // 使用者拒絕授權時會帶 error=ACCESS_DENIED
return
}
// 1. 用授權碼換取 Token
const tokenRes = await fetch("https://api.line.me/oauth2/v2.1/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
client_id: CHANNEL_ID,
client_secret: CHANNEL_SECRET,
code_verifier: saved.codeVerifier,
}),
})
if (!tokenRes.ok) {
res.status(502).send("Token exchange failed")
return
}
const { access_token, id_token } = (await tokenRes.json()) as { access_token: string; id_token: string }
// 2. 交給 LINE 驗證 ID Token(簽章、Channel ID、效期、nonce)
const verifyRes = 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, client_id: CHANNEL_ID, nonce: saved.nonce }),
})
if (!verifyRes.ok) {
res.status(401).send("Invalid ID token")
return
}
const claims = (await verifyRes.json()) as { sub: string; name?: string; picture?: string }
// 3. 選用:用 Access Token 取得最新個人檔案
const profile = await fetch("https://api.line.me/v2/profile", {
headers: { Authorization: `Bearer ${access_token}` },
}).then((r) => r.json() as Promise<{ userId: string; displayName: string; pictureUrl?: string }>)
// claims.sub 是此 Provider 下的使用者 ID,在這裡建立登入 session
console.log(claims.sub, profile.displayName)
res.redirect("/")
})
app.listen(3000)
補充說明:
state必須是每次登入都重新產生的英數字隨機值,並與瀏覽器綁定(這個範例使用 HttpOnly Cookie);不一致的 Callback 一律拒絕。emailscope 要等 LINE 核准 Channel 的電子郵件存取權限申請後才能使用。- 在授權 URL 加上
bot_prompt=normal或bot_prompt=aggressive,就能在登入時邀請使用者將 LINE 官方帳號加入好友,Callback 也會多帶friendship_status_changed。
#LIFF:把 ID Token 交給伺服器驗證
LIFF 應用程式就是一般的網頁應用程式,部署在 HTTPS Endpoint 上,由 LINE 以 LIFF 瀏覽器開啟。從 npm 安裝 @line/liff 時請使用 2.31.1 以上版本(LINE 於 2026 年 9 月 30 日因安全性問題將 v2.20.0 到 v2.31.0 列為不建議使用);若從 CDN 載入 https://static.line-scdn.net/liff/edge/2/sdk.js,則一律會取得最新的 2.x 版。
// LIFF app(瀏覽器端):取得 ID Token,交給伺服器驗證
import liff from "@line/liff"
await liff.init({ liffId: "1234567890-AbcdEfgh" })
if (!liff.isLoggedIn()) {
liff.login() // 外部瀏覽器才需要;在 LIFF 瀏覽器中使用者已登入
} else {
const idToken = liff.getIDToken() // 需要 openid scope
await fetch("/api/me", { method: "POST", headers: { Authorization: `Bearer ${idToken}` } })
}
不要把 liff.getProfile() 的結果當成身分證明傳給伺服器,因為請求內容很容易偽造。請傳送 ID Token(或 Access Token),在伺服器端用與 LINE Login 一節相同的 POST /oauth2/v2.1/verify 驗證,client_id 填入 LIFF 應用程式所在 LINE Login Channel 的 Channel ID。畫面尺寸、liff.sendMessages()、分享對象選擇器、外部瀏覽器與部署方式,請看 LIFF 應用程式開發指南。
#Webhook:簽章驗證與事件處理
聊天室裡發生事件時(收到訊息、新增好友、點擊 Postback 按鈕等),LINE 會向 Messaging API Channel 唯一的 Webhook URL 發送 HTTPS POST。請在 Console 的 Messaging API 分頁設定 URL,按下 Verify,再開啟 Use webhook。URL 必須使用一般瀏覽器信任的憑證機構所簽發的憑證,自簽憑證無法使用。如果由你的伺服器回應使用者,請在 LINE 官方帳號管理後台關閉預設的加入好友歡迎訊息與自動回應訊息,避免使用者收到兩則回覆。
LINE 會為每個請求簽章:x-line-signature 標頭是以 Channel secret 為金鑰、對原始請求內容計算的 HMAC-SHA256,再以 Base64 編碼。請在解析 JSON 之前,用實際收到的位元組驗證。若先解析再重新序列化(例如在 express.json() 之後執行 JSON.stringify(req.body)),位元組就會改變,導致驗證失敗。
// webhook.ts:不用 SDK 接收事件(Express 5)
import express from "express"
import { createHmac, timingSafeEqual } from "node:crypto"
import { reply } from "./line-api.js"
const CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET!
const app = express()
function isValidSignature(rawBody: Buffer, signature: string | undefined) {
if (!signature) return false
const expected = createHmac("sha256", CHANNEL_SECRET).update(rawBody).digest()
const received = Buffer.from(signature, "base64")
return received.length === expected.length && timingSafeEqual(received, expected)
}
type LineEvent = {
type: string
webhookEventId: string
deliveryContext: { isRedelivery: boolean }
replyToken?: string
source?: { type: string; userId?: string; groupId?: string; roomId?: string }
message?: { id: string; type: string; text?: string }
postback?: { data: string }
}
// express.raw 保留 LINE 簽章時的原始位元組;這個路由不要用 express.json()
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const rawBody = req.body
if (!Buffer.isBuffer(rawBody) || !isValidSignature(rawBody, req.get("x-line-signature"))) {
res.sendStatus(401)
return
}
const { events } = JSON.parse(rawBody.toString("utf8")) as { events: LineEvent[] }
res.sendStatus(200) // 盡快回應,events 為 [](連線檢查)時也一樣
for (const event of events) handleEvent(event).catch((err) => console.error(err))
})
const seen = new Set<string>() // 正式環境請用設有 TTL 的 Redis 或資料庫
async function handleEvent(event: LineEvent) {
if (seen.has(event.webhookEventId)) return // 重新傳送或重複的事件
seen.add(event.webhookEventId)
switch (event.type) {
case "message":
if (event.replyToken && event.message?.type === "text") {
await reply(event.replyToken, [{ type: "text", text: `您說:${event.message.text}` }])
}
break
case "follow":
if (event.replyToken) await reply(event.replyToken, [{ type: "text", text: "感謝您加入好友!" }])
break
case "postback": {
const data = new URLSearchParams(event.postback?.data) // 例如 "action=book&slot=10:00"
console.log("postback", data.get("action"), data.get("slot"))
break
}
case "unfollow":
// 沒有 replyToken:使用者已封鎖,停止傳訊給 event.source?.userId
break
}
}
app.listen(Number(process.env.PORT ?? 3000))
這段程式碼依循的 LINE 官方文件重點:
- 所有合法請求都回傳
200,包括 LINE 為了確認連線而送來的"events": []請求。 - 以非同步方式處理事件,避免較慢的處理拖累後續的 Webhook。若使用回應送出後就停止執行的 Serverless 平台,請先把事件放進佇列再回應。
- LINE 不公開發送 Webhook 的 IP 位址,因此簽章就是驗證身分的方法。
- Webhook 重新傳送功能預設為關閉。在 Console 開啟後,LINE 會重新傳送沒有收到 2xx 回應的 Webhook,同一個事件也可能收到兩次;請略過已處理過的
webhookEventId,並以deliveryContext.isRedelivery判斷是否為重新傳送。
| 事件 | 觸發時機 | Reply token |
|---|---|---|
message | 使用者傳送文字、圖片、影片、語音、檔案、位置資訊或貼圖 | 有 |
follow | 使用者加入好友或解除封鎖 | 有 |
unfollow | 使用者封鎖帳號 | 無 |
postback | 使用者點擊 Postback 動作(按鈕、圖文選單、快速回覆) | 有 |
unsend | 使用者收回訊息 | 無 |
join / leave | 帳號加入群組或聊天室/離開或被移出 | join:有,leave:無 |
memberJoined / memberLeft | 有人加入/離開帳號所在的群組 | joined:有,left:無 |
messageEdited | 使用者在群組聊天中編輯文字訊息(2026 年 8 月 12 日新增) | 有 |
videoPlayComplete、beacon、accountLink、membership | 影片看完、LINE Beacon、帳號連結、會員方案 | 有 |
用官方 SDK(line-bot-sdk v3,Python 3.10 以上)撰寫同樣的 Webhook 如下,WebhookHandler.handle() 會驗證簽章,不符時拋出 InvalidSignatureError:
# app.py:同樣的 Webhook 改用 Python(line-bot-sdk v3,Python 3.10 以上)
import os
from flask import Flask, request, abort
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 FollowEvent, 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) # 解析 JSON 之前的原始 body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(401)
return "OK"
@handler.add(MessageEvent, message=TextMessageContent)
def on_text(event):
with ApiClient(configuration) as api_client:
MessagingApi(api_client).reply_message(
ReplyMessageRequest(
reply_token=event.reply_token,
messages=[TextMessage(text=f"您說:{event.message.text}")],
)
)
@handler.add(FollowEvent)
def on_follow(event):
with ApiClient(configuration) as api_client:
MessagingApi(api_client).reply_message(
ReplyMessageRequest(
reply_token=event.reply_token,
messages=[TextMessage(text="感謝您加入好友!")],
)
)
#安全性、速率限制與錯誤處理
安全檢查清單
- 每個 Webhook 都以原始內容驗證簽章,並使用固定時間比對。
- Channel secret 與 Token 只放在伺服器端的環境變數或密鑰管理服務中,懷疑外洩的 Token 立即撤銷。
- 優先使用 Channel access token v2.1 或短效型 Token;若仍使用長效型 Token,請在 Console 的 Security 分頁限制可呼叫 API 的 IP 位址。
- LINE Login 與 LIFF 的 ID Token 一律在伺服器端驗證,不要相信瀏覽器傳來的使用者 ID。
- 全程使用 HTTPS,使用者輸入在儲存或顯示前都要先驗證與跳脫。
- Endpoint 路徑要與官方文件完全一致:LINE 於 2026 年 8 月 17 日提醒,結尾多一個斜線之類的差異可能導致無法預期的行為。
速率限制(以 Channel 為單位,2026 年 10 月)
| Endpoint | 上限 |
|---|---|
| 回覆、推播及大部分 Endpoint | 每秒 2,000 次請求 |
| Multicast(2025 年 4 月 23 日起) | 每秒 200 次請求 |
| 取得圖文選單清單(2026 年 5 月 26 日起) | 每秒 10 次請求 |
| 顯示載入動畫 | 每秒 100 次請求 |
| Broadcast、Narrowcast、統計資料、測試 Webhook Endpoint | 每小時 60 次請求 |
| 透過 API 建立或刪除圖文選單 | 每小時 100 次請求 |
錯誤會以 JSON 回傳,包含 message,請求格式不正確時另有 details 陣列:
| 狀態碼 | 意義 | 處理方式 |
|---|---|---|
| 400 | 請求不正確,或 Invalid reply token(Token 已使用或過期) | 依 details 修正內容,不要原封不動重試 |
| 401 | Channel access token 無效 | 重新發行 Token |
| 403 | 帳號或方案無法使用此 Endpoint | 檢查方案與權限 |
| 409 | 同一個 X-Line-Retry-Key 已被受理 | 視為成功 |
| 413 | 請求超過 2 MB | 減少單次傳送的內容 |
| 429 | 超過速率限制,或已達每月訊息上限("You have reached your monthly limit.") | 間隔一段時間再重試,並檢查則數與方案 |
| 500 | LINE 端錯誤 | 間隔遞增後重試,推播請沿用同一個 retry key |
import { randomUUID } from "node:crypto"
import { LineApiError, push, type LineMessage } from "./line-api.js"
export async function pushWithRetry(to: string, messages: LineMessage[], attempts = 3) {
const retryKey = randomUUID() // 每次重試都用同一個 key
for (let attempt = 1; ; attempt++) {
try {
return await push(to, messages, retryKey)
} catch (err) {
if (err instanceof LineApiError && err.status === 409) return // 先前的請求已被受理
const retryable = !(err instanceof LineApiError) || err.status === 429 || err.status >= 500
if (!retryable || attempt >= attempts) throw err
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000))
}
}
}
也請把回應標頭中的 x-line-request-id 記錄下來(上面的範例存在 LineApiError.requestId),向 LINE 回報問題時會需要提供。
#LineBot.pro 適合用在哪裡
LineBot.pro 不是 SDK,也不是 API 服務供應商,沒有公開 API;這篇教學的所有內容都在你自己的程式碼與 Channel 上執行。不過有兩個地方可以幫你省時間:
- 訊息與選單設計:AI 產生器可以製作圖文訊息(含主圖、標題、內文與按鈕的 Flex 泡泡)或有 1 到 6 個點擊區的圖文選單圖片。把 Flex JSON 複製到上面的
reply或push即可傳送,或下載選單圖片與 JSON 自行上傳。圖文訊息每次 8 點、圖文選單 10 點,新註冊送 50 點,點數採一次性購買、永不過期(價格)。立即產生 Flex Message - 不寫程式的機器人:如果關鍵字回覆就夠用,我們的 LINE 聊天機器人工具會在一對一聊天中,用你核准過的圖文訊息卡片回覆符合關鍵字的訊息;不符合的訊息可選擇開啟 AI 意圖判斷(每檢查一則訊息 1 點,AI 只負責挑選卡片,不會自行撰寫回答)。這個工具使用自己的 Webhook URL,而每個 Channel 只能設定一個 Webhook URL,所以它會取代你在該 Channel 上的 Webhook 伺服器,無法同時並行。
若要把 Messaging API 串接到自家系統,或開發 LIFF 應用程式、LINE MINI App,我們提供 LINE 客製化開發與 LINE 應用程式開發服務,歡迎附上需求與我們聯絡。
#常見問題
Q: LINE API 可以做什麼? 透過 Messaging API,LINE 官方帳號可以回覆使用者,傳送推播、Multicast、Broadcast、Narrowcast 訊息,管理圖文選單,並取得個人檔案與統計資料。LINE Login 為網站和 App 加上「使用 LINE 登入」,LIFF 在 LINE 內執行網頁應用程式,LINE MINI App 則把這些網頁應用程式上架成 LINE 內的服務。
Q: LINE Messaging API 要收費嗎? 呼叫 API 本身不收費。回覆訊息完全不計入則數;推播、Multicast、Broadcast、Narrowcast 則依收件人數計入方案的每月訊息則數。截至 2026 年 10 月,免費方案每月可傳送的則數為台灣 200 則、日本 200 則、泰國 300 則。付費方案請看費用指南。
Q: 為什麼 Webhook 簽章驗證一直失敗?
最常見的原因是驗證前內容已被解析成 JSON(例如經過 express.json()),導致計算雜湊的位元組與 LINE 簽章時不同。其他原因包括:用了其他 Channel 或已重新發行前的 Channel secret、代理伺服器改寫了內容,或演算法錯誤。請用原始內容計算 HMAC-SHA256,再以 Base64 比對。
Q: 現在還能用 LINE Notify 嗎? 不行。LINE Notify 已於 2025 年 3 月 31 日終止服務。請改用 LINE 官方帳號透過 Messaging API 傳送推播訊息,收件的使用者必須是該帳號的好友。
Q: 使用 LINE API 一定要用 SDK 嗎? 不一定。如本文所示,所有 API 都是一般的 HTTPS 與 JSON。官方 SDK(Node.js、Python、PHP、Java、Go、Ruby)提供型別、簽章驗證函式與正確的 Endpoint 路徑;其他程式語言則可使用 LINE 公開的 OpenAPI 規格。
Q: LIFF 和 LINE Login 有什麼不同? LINE Login 讓使用者登入 LINE 之外的自家網站或 App;LIFF 則是在 LINE 內執行你的網頁應用程式,底層使用 LINE Login,因此 LIFF 應用程式要加在 LINE Login Channel(或 LINE MINI App Channel)中。
下一步:
- 用 SDK 實作同樣的機器人:@line/bot-sdk Express 中介軟體範例
- 進一步設計對話流程:LINE Bot 開發教學
- 打造 LINE 內的網頁介面:LIFF 應用程式開發指南
- 掌握平台最新變動:LINE Messaging API 2026 更新
- 想交給專人開發:LINE 客製化開發
關於 LineBot.pro
LineBot.pro 是專為 LINE 官方帳號打造的 AI 工具,可製作圖文選單、圖文訊息與橫幅,群發行銷活動,並依關鍵字自動回覆。這些指南由我們的團隊根據 LINE Messaging API 的實務經驗撰寫。
聯絡我們相關文章

@line/bot-sdk Express 中介軟體實作範例(v11)
@line/bot-sdk v11 的 Express + TypeScript Webhook 完整範例:middleware() 該放哪裡、validateSignature 在 Node.js、Python、PHP 的用法、搭配 Flex Message 的 replyMessage、sentMessages 與引用 token,以及實際會遇到的錯誤。
閱讀更多
LIFF 開發教學 2026:Next.js 與 TypeScript 實作
2026 年版 LIFF(LINE Front-end Framework)應用開發教學:頻道與 Endpoint URL 設定、liff.init 規則、伺服器端驗證 ID Token、sendMessages、shareTargetPicker、深層連結、用 LIFF CLI 測試,以及 v2.31.1 安全性更新。
閱讀更多
LINE Bot 開發教學 2026:Node.js 與 Python 實作
用 Messaging API 一步步開發 LINE Bot:啟用 Channel、驗證 Webhook 簽章、用 Flex Message 與快速回覆回應、以 postback 完成預約流程、加入只根據 FAQ 回答的 AI,最後部署上線。附 Node.js(@line/bot-sdk v11)與 Python(line-bot-sdk v3)程式碼。
閱讀更多