上一篇帶大家透過「Webhook 模式」成功架設了專屬的 LINE Bot 代理人。相信大家在體驗基礎功能後,對於 LINE Bot Webhook 還能監聽哪些事件、支援哪些類型的訊息回覆,以及如何確保後端收到的訊息確實來自 LINE 平台等安全性議題更感興趣。
本文將著重於實現以下核心功能:
本日程式碼的範例連結
Push Message(主動推播訊息)
LINE Official Account 方案說明(不同方案會有不同的免費訊息推播限額)
- 主動推播每一位用戶都會消耗 1 則免費訊息的額度。
例如:群組 50 個人,推播一次就會消耗 50 則免費訊息額度。- 超過計畫中的免費訊息額度時,只有高用量可以發送額外訊息
![]()
Day 1
所提到的【 Chat 模式 】
不會消耗推播免費訊息(free messages)
的額度
Reply Message(被動回覆訊息)
Reply Token
使用,且只能被使用一次Reply Token 說明:
- 保證在收到 Webhook 事件後 1 分鐘之內有效,超過 20 分鐘失效(在這期間不保證有效)
- 一個 Reply Token 最多可以回覆 5 則訊息
- 使用 Reply Message 不會消耗免費訊息額度
Day 2
的範例中,我們成功實現了「當使用者傳送訊息時,能夠透過 Webhook 回傳 Hello World!」,但實作過程中卻沒有使用到 Channel Secret
,這部分衍生出一個重要的安全議題:
如何確認請求來源確實是 LINE 平台?
如下圖所示,如果第三方惡意服務透過中間人攻擊的方式,攔截到 LINE 傳遞給後端伺服器的資料,是否就能夠任意修改傳入的內容,進而對系統造成威脅?
為了解決這個安全隱憂,LINE 在傳遞事件資訊到後端時,會使用 Channel Secret
對請求內容進行數位簽章,並將產生的簽章放置在 HTTP 請求標頭的 x-line-signature
屬性中。透過這個機制,後端伺服器便能在處理請求之前,先行驗證資料來源是否真的來自 LINE 平台。
接下來我們將分兩個部分說明:
用戶使用 LINE 應用程式傳送訊息至架設好的官方帳號,透過終端機查看印出的訊息
// 略
/**
* Webhook 端點處理器
* 接收來自 LINE Platform 的事件通知
* @param body LINE Platform 傳送的 Webhook 請求本體
* @returns 處理完成的回應訊息
*/
@Post('/webhook')
async handleWebhook(@Body() body: WebhookRequestBody): Promise<string> {
console.log(
'Webhook 從 line platform 接收到的資訊:',
JSON.stringify(body, null, 2),
);
// 略
return 'Webhook processed successfully';
}
終端機印出的訊息:
複製 Step 1
後端伺服器接收到 LINE 平台事件訊息,模擬第三方駭客擷取了 LINE 平台發送給後端伺服器的資訊,並且將內容進行串改在傳送給後端伺服器。
顯示
Webhook processed successfully
代表發送至後端的請求處理成功
預設情況下,NestJS 會將傳入的請求資料自動轉換成 JSON
格式,再傳遞到後續的路由處理器中。然而,在進行 LINE 數位簽章驗證時,必須使用原始的 Buffer
資料進行驗證,而非經過解析的 JSON
物件。因此我們需要針對 /webhook
路由保留原始資料格式。
原始資料轉換成
json
的部分,可以想像在express
執行express.json()
的效果
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as express from 'express';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
bodyParser: false,
});
app.use('/webhook', express.raw({ type: 'application/json' }));
app.use(express.json());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
執行以下指令創建 middleware
檔案:
創建路徑在 src/middleware/line.middleware.ts
nest g middleware line middleware --flat --no-spec
line middleware.ts
// 略
@Injectable()
export class LineMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
use(req: Request, res: Response, next: NextFunction) {
const channelSecret = this.configService.get<string>('LINE_CHANNEL_SECRET');
if (!channelSecret) {
console.error('LINE_CHANNEL_SECRET 未設定');
return res.status(500).send({ status: 'Server error' });
}
try {
const signature = req.headers['x-line-signature'] as string;
if (!signature) {
console.error('缺少 X-Line-Signature header');
return res.status(401).send({ status: 'Unauthorized' });
}
const bodyBuffer = req.body;
// 用原始 Buffer 驗證簽名
if (!validateSignature(bodyBuffer, channelSecret, signature)) {
console.error('簽名驗證失敗');
return res.status(401).send({ status: 'Unauthorized' });
}
// 簽名驗證通過後,把 Buffer 轉換回 JSON 物件
req.body = JSON.parse(bodyBuffer.toString('utf8'));
// 往下繼續到 Controller
next();
} catch (error) {
console.error('LINE Middleware Error:', error);
return res.status(401).send({ status: 'Unauthorized' });
}
}
}
只要經過
/webhook
端點的請求,都會需要先透過Step 2 middleware
的檢查
app.module
// 略
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true })],
controllers: [AppController],
providers: [LineConfigProvider],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 針對 webhook 路由使用 LINE 中介軟體驗證
consumer.apply(LineMiddleware).forRoutes('webhook');
}
}
透過 Channel Secret
可以驗證請求來源確實來自 LINE 平台,防止第三方攔截或篡改內容,確保後端服務的安全性。
昨天我們完成了 Hello World 的回覆,接下來我們試著在使用者加入官方帳號的時候,設計一個招呼語並搭配 Reply Message
的方式來歡迎使用者的加入吧!
單聊的情境上來說常見使用的事件有以下三種:
1. Message Event(訊息事件)
- 情境:用戶發送任何類型的訊息時觸發
- 用途:回應用戶訊息,如自動回覆、客服應用等
2. Follow Event(加入好友事件)
- 情境:用戶首次加入好友或解除封鎖官方帳號時觸發
- 用途:發送歡迎訊息、新用戶導引、優惠活動
3. Unfollow Event(取消好友事件)
- 情境:用戶封鎖或刪除官方帳號時觸發
- 用途:記錄封鎖事件、分析用戶流失
當使用者加入 LINE Bot 時會觸發 Follow Event,此時通常會有一個招呼語功能。在 LINE official account
後台Resonse settings
中,Greeting message 是預設開啟的功能,點選「Greeting message settings」綠色連結即可在 GUI 介面上進行設定。
在 LINE Official Account 後台能夠設置基礎的自動回覆功能,支援多種訊息類型,包括:文字訊息、圖片、影音、卡片模板等。管理者可以設定歡迎訊息僅在好友初次加入時觸發。需要注意的是,如果未設置此功能,當使用者執行封鎖後再解除封鎖的操作時,系統會重新發送 LINE 平台的預設招呼語
然而,如果有更進階的應用情境,例如:需要發送客製化的 Flex Message
卡片,或是在用戶加入官方帳號後將資料存入自己的資料庫系統,就必須透過後端伺服器搭配 Webhook
的方式來進行開發。
以下我們試試看將招呼語的功能,透過 webhook 的方式在自己的後端伺服器設計吧!
在 app.controller.ts
中處理 LINE 平台事件時,建議統一透過 eventHandler
來處理對應的事件,這樣可以避免撰寫多個 if...else
判斷,提升程式碼的可讀性和維護性。
先前只有 MessageEvent
的對應處理,這邊可以加上 FollowEvent
、UnfollowEvent
的處理。
app.controller.ts
/// 略
const eventHandler = {
message: (event) => this.handleMessageEvent(event),
follow: (event) => this.handleFollowEvent(event),
unfollow: (event) => this.handleUnfollowEvent(event),
} satisfies Partial<HandlerMap>;
提醒要關閉 LINE Official Account 後台招呼語的功能,否則 LINE Official Account 設置會跟你自己建置的後端伺服器會同時觸發招呼語!
這邊舉例上是以回覆文字內容為主,但實際上可以結合更多客製化的應用,後續也會呈現結合資料庫的進階應用!
app.controller.ts
/**
* 用戶首次加入好友或解除封鎖官方帳號時觸發
* @param event 加入好友事件
*/
private async handleFollowEvent(event: FollowEvent): Promise<void> {
const { replyToken } = event;
await this.lineClient.replyMessage({
replyToken,
messages: [
{
type: 'text',
text: '恭喜你加入我們的官方帳號!',
},
],
});
}
在實作 LINE Bot 功能時,並非所有功能都需要透過 Webhook 自行開發。對於簡單常見的應用場景,例如文字描述搭配圖片介紹等基本互動,可以透過 LINE Official Account Manager 後台進行設置,無需撰寫任何程式碼。這樣的設計理念體現了 LINE Bot 開發的靈活性,讓開發者能夠根據需求選擇最適合的實作方式,即使不使用程式開發,也能透過官方提供的工具達到一定功能水準。