← 所有文章
教學

把 Webhook 文件丟給 Devin Desktop,20 分鐘搞定 LINE 訊息接收服務

Devin Desktop——2026 年初由 Windsurf 更名而來,搭載的 SWE-1.6 模型輸出速度達到 950 tokens/s——是目前我身邊開發者討論最多的 AI 編碼工具。用法很直接:把參考文件丟給它,描述你要什麼,迭代到能跑為止。我想試試這套流程用在 webhook API 上效果如何。

任務:寫一個 Python 服務,接收 LINE 訊息的 webhook 事件,結構化日誌輸出——那種小團隊會部署在訊息佇列或工單系統前面的 handler。不需要 LINE 官方帳號、不需要 Messaging API 憑證、不需要管理 channel access token。只要入站訊息以標準化 JSON 的形式到達。

最終成品

一個 FastAPI 服務(大約 40 行程式碼):

  1. 接收 UnifyPort 統一 webhook 的 message.received 事件
  2. 使用 signing_secret 進行 HMAC-SHA256 簽章驗證
  3. 將每則訊息以結構化 JSON 記錄日誌——provider、sender、text、timestamp
  4. 所有事件回傳 200,簽章無效回傳 401

耗時:約 20 分鐘。你需要一個 UnifyPort 工作區並連接 LINE 帳號(LINE App 掃碼即可),以及 Python 3.10+。

為什麼不直接用 LINE Messaging API?

官方路徑:

  1. 建立 LINE 官方帳號——需要 LINE Business ID,需要企業或實名個人認證
  2. 在 LINE Developers 主控台啟用 Messaging API——設定 channel、產生 channel access token、設定 webhook URL
  3. 解析 LINE 的 webhook payload:事件結構是 {"events": [{"type": "message", "message": {"type": "text", "text": "..."}, "source": {"userId": "U..."}}]}——巢狀的、LINE 專有格式
  4. 驗證簽章:使用 channel secret 校驗 x-line-signature——LINE 用的是 Base64 編碼的 HMAC-SHA256,不是 hex
  5. 處理 reply token:每個事件帶一個有效期 30 秒的 replyToken,超時回覆就靜默失敗

程式碼能跑,但只能跑 LINE。webhook payload 格式、簽章方式、reply token 機制——沒有一樣能複用到 WhatsApp 或 Telegram。加第二個平台就要從零開始做第二套整合。

UnifyPort 的非官方介面連接個人 LINE 帳號——掃碼連接,不需要官方帳號——將每則入站訊息標準化為 message.received 事件,和 WhatsApp、Telegram、TikTok、Zalo、X 的格式完全一致。一個 handler 涵蓋六個平台。

準備:把 API 文件餵給 Devin Desktop

開啟 Devin Desktop,建立新工作階段。第一次提問前,給它上下文:

  1. 貼上 UnifyPort webhook 文件中的 message.received 事件 payload:
    {
      "event": "message.received",
      "account_id": "acct_7kQnWx",
      "provider": "line",
      "from": "user_a3f82c",
      "text": "可以改到週五嗎?",
      "timestamp": 1751270400,
      "message_id": "line_msg_5e9d21"
    }
  2. 貼上 HMAC-SHA256 簽章驗證部分——x-unifyport-signature header、用 signing_secret 對原始請求主體計算 hex digest

Devin 將貼上的內容作為工作階段上下文索引。SWE-1.6 Fast 以 950 tok/s 處理,文件消化只需幾秒。Cursor、Claude Code、Copilot 也一樣——貼上或附加即可。工具可以換,文件才是關鍵。

建構過程

第一個 prompt——handler 骨架:

根據我剛貼上的 UnifyPort webhook 文件,寫一個 FastAPI 服務,POST /webhook 路由。當事件為 “message.received” 時,用 Python 的 logging 模組以結構化 JSON 記錄 provider、from、text 和 timestamp。所有事件回傳 200。用 uvicorn 啟動。

Devin 讀取上下文並產生:

from fastapi import FastAPI, Request
import logging, json

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    evt = await request.json()
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

十二行 handler。沒有 LINE 專用 import,沒有 linebot SDK,沒有 channel access token。事件結構夠扁平,不需要額外解析。

第二個 prompt——簽章驗證:

加上 HMAC-SHA256 簽章驗證。header 是 x-unifyport-signature,secret 從環境變數 UNIFYPORT_SIGNING_SECRET 讀取,HMAC 對原始請求主體 bytes 計算——不是重新序列化的 JSON。驗證失敗回傳 401。使用 timing-safe 比較。

Devin 加上驗證層:

import hmac, hashlib, os

SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]

async def verify_signature(request: Request) -> bytes:
    body = await request.body()
    sig = request.headers.get("x-unifyport-signature", "")
    expected = hmac.new(
        SIGNING_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(status_code=401, detail="invalid signature")
    return body

使用 hmac.compare_digest 進行時序安全比較,操作對象是 request.body()——原始 bytes——而不是重新序列化 request.json()

第三個 prompt——合併整理:

把簽章驗證合併到 webhook handler 裡。驗證通過後再將 body 解析為 JSON。加一個 GET /health 健康檢查。加上 uvicorn.run,port 8000。

完整的 server.py

from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, os, json, logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("inbound")

SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]

app = FastAPI()

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("x-unifyport-signature", "")
    expected = hmac.new(
        SIGNING_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(status_code=401, detail="invalid signature")

    evt = json.loads(body)
    if evt.get("event") == "message.received":
        logger.info(json.dumps({
            "provider": evt["provider"],
            "from": evt["from"],
            "text": evt["text"],
            "timestamp": evt["timestamp"],
            "message_id": evt["message_id"],
        }))
    return {"status": "ok"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

不到 40 行。簽章驗證、結構化日誌、可以直接部署在任何反向代理後面。

跑起來,看訊息進來

在 UnifyPort 工作區連接 LINE 帳號——LINE 支援掃碼認證,開啟 LINE App 掃一下就連上了。不需要 LINE 官方帳號、不需要 Business ID、不需要設定 Messaging API。

註冊一個 webhook 端點指向你的伺服器,設定 subscribed_events: ["message.received"]signing_secret。啟動服務:

UNIFYPORT_SIGNING_SECRET=your_secret python server.py

讓朋友傳一則測試訊息到你的 LINE。事件到達:

{
  "event": "message.received",
  "account_id": "acct_3Xk1wL",
  "provider": "line",
  "from": "user_a3f82c",
  "text": "可以改到週五嗎?",
  "timestamp": 1751270400,
  "message_id": "line_msg_5e9d21"
}

伺服器驗證簽章,輸出結構化日誌。如果簽章驗證失敗回傳 401——檢查 UNIFYPORT_SIGNING_SECRET 是否和 webhook 端點設定的一致。

加 WhatsApp,一行程式碼都不用改

在同一個工作區連接 WhatsApp 帳號,訂閱同一個 webhook 端點。WhatsApp 訊息到達時,格式完全一樣:

{
  "event": "message.received",
  "account_id": "acct_7kQnWx",
  "provider": "whatsapp",
  "from": "user_d4f29a",
  "text": "訂單確認,週一出貨",
  "timestamp": 1751270460,
  "message_id": "wa_msg_8b3e71"
}

同一個 handler,同樣的結構化日誌。provider 欄位從 "line" 變成 "whatsapp",程式碼路徑完全一致。LINE Messaging API 的 handler 要加 WhatsApp 就得從零開始做一套完全不同的整合——不同的 webhook payload、不同的簽章格式、不同的 SDK。這個 handler 涵蓋六個平台,因為事件結構不變。

這就是這套流程:把 UnifyPort API 文件貼給 Devin Desktop,描述你要的 handler,迭代到能跑。工具寫程式碼,標準化 webhook 讓程式碼適用於所有平台。換成 Cursor、Claude Code 或 Copilot 也一樣——文件不變,handler 不超過 40 行。