上線注意事項
用 GAS 當 LINE Bot 後台,個人或小團隊用起來很順,但它有幾個先天限制。這頁把會踩的坑集中講:無法驗證簽章、重複事件、時間限制、額度,以及什麼時候該換平台。
無法驗證 Webhook 簽章
LINE 官方要求:每個 Webhook 請求都帶 x-line-signature 標頭,內容是用 Channel secret 對原始請求內容做 HMAC-SHA256 再 Base64 的結果;機器人必須先比對簽章,不符或沒有簽章就不要處理。
問題是 GAS Web App 的 doPost(e) 讀不到請求標頭(e 裡沒有 headers),所以簽章驗證在 GAS 裡做不到。後果是:任何知道你 /exec 網址的人,都能偽造一個像 LINE 事件的請求,觸發你的機器人邏輯、消耗你的額度。
實務上的減輕辦法,由簡到嚴:
| 做法 | 效果 | 限制 |
|---|---|---|
網址後面加一段長隨機字串,doPost 檢查 |
不知道網址的人打不進來 | 網址外流就失效;密碼在網址裡,容易出現在日誌 |
敏感操作只認白名單 userId |
偽造事件也不能做管理動作 | 事件內容仍可被偽造 |
| 機器人回覆不洩漏私人資料 | 就算被偽造,攻擊者也拿不到東西 | 仍會消耗額度 |
| 前面放一層中繼服務驗簽,再轉給 GAS | 真正符合官方要求 | 多一個要維護的服務 |
網址加密碼的寫法:把 Webhook URL 設成 .../exec?key=長隨機字串(設定後用 Verify 確認仍然通),再在 doPost 開頭比對:
function doPost(e) {
const key = PropertiesService.getScriptProperties().getProperty("WEBHOOK_KEY");
if (e.parameter.key !== key) return okResponse(); // 不處理,也不透露原因
// ...原本的處理
}
function okResponse() {
return ContentService.createTextOutput(JSON.stringify({ ok: true }))
.setMimeType(ContentService.MimeType.JSON);
}
Web App 也無法自訂 HTTP 狀態碼,所以擋掉的請求對方看起來仍是成功,不是 401。
要做到真正驗簽,標準架構是:LINE → 你自己的中繼服務(例如 Cloud Run 或 Cloudflare Workers)→ 用 Channel secret 驗證 x-line-signature → 通過才轉發給 GAS,並帶上上面那種共用密碼。這是進階題,本站不展開;如果機器人會處理金錢、個資或商業用途,建議一開始就用這個架構,或直接不用 GAS。
重複事件與 302 回應
LINE 要求 Webhook 回 2xx。網路不穩或回應不是 2xx 時,LINE 可能重送同一個事件(需在主控台開啟 Webhook redelivery);重送的事件內容相同,webhookEventId 與 replyToken 都不變,只有 deliveryContext.isRedelivery 變成 true。
另外,GAS Web App 收到 POST 後,實際上會先回應一個 302 轉址,把結果放在另一個網址。有些 Webhook 發送方(例如金流)會把 3xx 當成失敗而重送。LINE 不在乎回應本文,許多社群教學直接用 GAS 接 LINE 也能運作,但這不是官方保證的行為,請以你自己的 Verify 與實測為準。
不論原因,機器人都必須能承受同一事件被送兩次。做法是用 webhookEventId 去重:
function doPost(e) {
const body = JSON.parse(e.postData.contents);
(body.events || []).forEach((event) => {
if (isDuplicate(event.webhookEventId)) return;
try {
handleEvent(event);
} catch (err) {
console.error(err.stack); // 一個事件出錯,不影響同批其他事件
}
});
return okResponse();
}
function isDuplicate(eventId) {
if (!eventId) return false;
const cache = CacheService.getScriptCache();
if (cache.get(eventId)) return true;
cache.put(eventId, "1", 21600); // 記 6 小時(快取的最長時間)
return false;
}
這個做法在兩次請求「完全同時」到達時仍可能漏網;要更嚴格就在 isDuplicate 外面包 LockService。「記帳」這類會寫入資料的機器人,去重比回覆型的更重要,否則同一筆會被記兩次。
時間限制
| 限制 | 影響 | 對策 |
|---|---|---|
replyToken 收到後約 1 分鐘內要用 |
處理太久,回覆會失敗 | 快速的事同步做完再 reply |
| 單次執行 6 分鐘 | 長工作被終止 | 先 reply「處理中」,慢的部分切段續跑,見 效能與執行限制 |
| Web App 冷啟動 | 久沒人用的第一則訊息回得慢 | 可接受就好;不能接受就換平台 |
| 官方說時限可能變動 | 別依賴精確秒數 | 越早回覆越好 |
處理超過一分鐘的工作時,不能靠 reply,必須在做完後用 push,而 push 會消耗額度。
額度
兩邊各有一套:
LINE 端(台灣地區官方帳號)
| 訊息類型 | 是否計費 |
|---|---|
| Reply(回覆) | 免費,不計入則數 |
| Push、Multicast、Broadcast、Narrowcast | 計入每月則數 |
台灣目前的方案:輕用量月費 0 元、每月 200 則免費;中用量 800 元、3,000 則;高用量 1,200 元、6,000 則(皆未稅)。輕用量與中用量用完就不能再發,只有高用量可以加購。LINE 已公告方案將於 2026 年 11 月 1 日調整,請以 LINE Biz-Solutions 最新資訊為準。
GAS 端(一般 Google 帳號)
- 每次
reply/push都是一次UrlFetchApp,每天上限 20,000 次(Workspace 100,000 次)。一天回覆超過兩萬則就會撞牆。 - 同時執行數每位使用者 30 個。因為 Web App 通常是「以我執行」,所有人的訊息都算在你一個人頭上。訊息瞬間大量湧入時,超過上限的請求可能失敗。
- 全部用量都吃你的帳號配額,如果同一帳號還跑著其他腳本,會互相排擠,見 效能與執行限制。
金鑰與部署管理
- Channel access token 只放 Script Properties。不小心貼到公開的地方,立刻到 LINE Developers Console 重新發行,舊的即失效。
- 官方建議使用有到期時間的 token(v2.1)。長期不換的 token 一旦外流,風險較高。
- 每個專案的版本數上限是 200(見配額表),定期到 管理部署作業 清掉不用的舊版本。
- 用 clasp 把程式放進 Git,改動才有紀錄可回溯。不要把 token 提交進 Git。
什麼時候該換平台
| 情況 | 建議 |
|---|---|
| 需要真正驗證簽章 | 中繼服務驗簽,或整個搬到 Cloud Run 等 |
| 訊息量大(每天上萬則)或要低延遲 | 換有常駐服務的平台 |
| 資料要交易、關聯查詢 | 換資料庫,不再用試算表 |
| 需要記錄使用者對話、複雜狀態機 | 用官方 SDK(Node.js、Python 等)寫成一般後端 |
GAS 的定位是「最快做出可用的原型」與「小規模自用」。用 GAS 驗證想法,需求長大了再搬,邏輯(指令路由、資料處理)多半可以直接帶過去。
上線檢查清單
- [ ] 加好友歡迎訊息與自動回應訊息已關閉
- [ ] Token 在 Script Properties,程式碼裡沒有任何金鑰
- [ ] 已決定簽章驗證的處理方式(至少有網址密碼)
- [ ]
webhookEventId去重已加上 - [ ] 每個事件外層有
try/catch,錯誤有記錄 - [ ] 有
default分支,看不懂的輸入會回提示 - [ ] 算過 Push 用量是否在免費額度內
- [ ] 最新版本已部署,用真的手機測過一遍