# 從 LINE Bot 實作理解 Webhook 機制 ## 為什麼寫這篇 最近在做 LINE Bot,發現 Webhook 這個東西雖然概念簡單,但實際串接時還是踩了不少坑。 一開始照著文件設定,Bot 就是不回應。檢查 log 發現 LINE 有發請求過來,但訊息就是發不出去。後來才發現是 `replyToken` 過期了——因為我處理太慢,超過 5 秒才回 200,LINE 就放棄重試了。 還有一次是使用者反應「為什麼傳一次訊息,機器人回三次?」查了半天才知道是網路抖動造成 LINE 重送 Webhook,而我沒做冪等性處理 (同一個操作執行多次,結果都跟執行一次一樣)。 這些坑讓我重新思考 Webhook 的設計邏輯:為什麼要快速回應?為什麼要驗證簽名?為什麼要處理重複請求?這篇筆記記錄了實作過程中的一些心得。 --- ## 如果不用 Webhook 會怎樣? 假設 LINE 不提供 Webhook,我們只能用輪詢 (Polling) 的方式: ```python # 輪詢方式 - 持續詢問 while True: response = requests.get('https://api.line.me/v2/messages') if response.json()['has_new_message']: messages = response.json()['messages'] for msg in messages: process_message(msg) time.sleep(1) # 每秒問一次 ``` ### 輪詢的問題 | 問題 | 說明 | |------|------| | **資源浪費** | 即使沒訊息也要一直發請求,99% 的請求都是白費的 | | **延遲** | 輪詢間隔 1 秒,訊息最多延遲 1 秒才收到 | | **API 限制** | 高頻率輪詢容易被 rate limit | | **伺服器負擔** | 如果有 1000 個 Bot,LINE 每秒要處理 1000 次查詢 | ### Webhook 的優勢 ```python # Webhook - 事件驅動 @app.post('/webhook') async def webhook(request: Request): event = await request.json() process_message(event) return {'status': 'ok'} ``` | 優勢 | 說明 | |------|------| | **即時** | 訊息一到立刻通知,零延遲 | | **省資源** | 只在有事件時才發請求 | | **可擴展** | 不管多少 Bot,LINE 只在需要時才發通知 | 簡單說就是:**Pull = 我問你,Push = 你通知我** --- ## LINE Bot 的 Webhook 機制 ### 架構圖 ``` 使用者 → LINE Platform → Webhook → 你的 Bot 後端 → LINE Messaging API ``` 整個流程: 1. 使用者傳訊息到 LINE 2. LINE Platform 發送 POST 請求到你設定的 Webhook URL 3. 你的 Bot 處理訊息並回傳 200 OK 4. Bot 透過 LINE Messaging API 回覆訊息 --- ## 實作細節與陷阱 ### 1. 簽名驗證 LINE 會在 Header 加上 `X-Line-Signature`,用來驗證請求確實來自 LINE Platform。 ```python def validate_signature(body: bytes, signature: str) -> bool: """驗證 LINE Webhook 簽名""" hash_value = hmac.new( CHANNEL_SECRET.encode('utf-8'), body, hashlib.sha256 ).digest() return base64.b64encode(hash_value).decode('utf-8') == signature ``` 為什麼需要驗證? 任何知道你 Webhook URL 的人都能發送偽造請求。不驗證簽名的話,攻擊者可以假冒 LINE 發送惡意資料。 ### 2. 快速回覆與非同步處理 LINE 要求在 **5 秒內回覆 200 OK**,否則會判定 Webhook 失敗並重試。 ```python # ❌ 錯誤:同步處理可能超時 @app.post('/webhook') async def callback(request: Request): body = await request.body() events = json.loads(body)['events'] for event in events: process_complex_task(event) # 可能超過 5 秒 return 'OK' # 太慢了 # ✅ 正確:先回覆,再處理 @app.post('/webhook') async def callback(request: Request): body = await request.body() # 立即回覆 asyncio.create_task(process_events(body)) return 'OK' async def process_events(body: bytes): events = json.loads(body)['events'] for event in events: await process_complex_task(event) ``` 或者使用訊息佇列: ```python import redis from rq import Queue q = Queue(connection=redis.Redis()) @app.post('/webhook') async def callback(request: Request): body = await request.body() # 丟到背景處理 q.enqueue(process_events, body) return 'OK' ``` ### 3. 冪等性處理 網路不穩定時,LINE 可能重送相同的 Webhook。必須確保重複處理不會產生副作用。 ```python import redis redis_client = redis.Redis() @handler.add(MessageEvent, message=TextMessage) def handle_message(event): message_id = event.message.id # 檢查是否已處理 if redis_client.exists(f'processed:{message_id}'): return # 標記為已處理 (TTL 24 小時) redis_client.setex(f'processed:{message_id}', 86400, '1') # 實際處理邏輯 process_message(event) ``` 或使用 `replyToken` 的特性:每個 token 只能用一次,LINE 會自動處理重複。 ### 4. Reply vs Push LINE Bot 有兩種發訊息方式: **Reply (使用 replyToken)**: ```python line_bot_api.reply_message( event.reply_token, TextSendMessage(text='回覆訊息') ) ``` - 只能在收到 Webhook 後立即使用 - 每個 replyToken 只能用一次 - 不計費 **Push (使用 userId)**: ```python line_bot_api.push_message( user_id, TextSendMessage(text='主動推播') ) ``` - 可以隨時主動發送 - 需要 user_id - 計費(免費額度外) --- ## 開發環境設定 ### 本地開發:使用 ngrok LINE Webhook 必須是公開的 HTTPS 網址,開發時可用 ngrok: ```bash # 啟動 Bot uvicorn main:app --reload --port 8000 # 另一個 terminal ngrok http 8000 ``` ngrok 是一個反向代理服務,可以將本地開發環境下的服務臨時公開到網際網路,提供一個公開的網址讓外部連線存取。 會提供類似 `https://abc123.ngrok.io` 的網址,填入 LINE Developers Console。 --- ## 總結 Webhook 的核心就是**事件驅動的 HTTP 回調**: 1. 註冊公開的 HTTPS 端點 2. 對方在事件發生時發送 POST 請求 3. 快速回覆 200 OK 4. 非同步處理實際邏輯 理解這個模式後,不只是 LINE Bot,許多第三方服務整合(GitHub、Stripe、Discord)都是類似的概念。 重點在於: - **安全性**:驗證請求來源 - **可靠性**:處理重試與冪等性 - **效能**:先回覆,再處理 --- ## 參考資源 - https://medium.com/@justinlee_78563/line-bot-%E7%B3%BB%E5%88%97%E6%96%87-%E4%BB%80%E9%BA%BC%E6%98%AF-webhook-d0ab0bb192be - https://ithelp.ithome.com.tw/m/articles/10193212 - [LINE Messaging API 文件](https://developers.line.biz/en/docs/messaging-api/) - [ngrok](https://ngrok.com/)