← 所有文章
教學

我把 Webhook 文件丟給 AI 編碼助手,它直接做出了一個能跑的自動回覆機器人 — UnifyPort

你一定熟悉這套流程。待辦清單上躺著一個訊息整合的工作,你沒有把文件從頭讀到尾,而是直接打開 Cursor——或者 Claude Code、Windsurf——開始敲提示詞。2026 年,大多數人就是這樣起一個專案的。真正的問題不是你會不會用 AI「氛圍編程」(vibe-coding),而是你對接的那個 API,有沒有給助手足夠的東西去把工作做乾淨

這是一份開發實錄。讀完你會得到一個能跑的自動回覆機器人:一個 webhook 接收端,對每一則入站訊息驗證簽章,再透過單一的傳送端點回覆——大約一個下午的工作量,而且大部分是助手做的。它之所以快,是因為 UnifyPort 的 webhook 是正規化的:只有一種事件結構要學,只有一個端點要回呼,助手沒什麼好猜的。

準備:先把文件餵給助手

最關鍵的一步,恰恰是大家最愛跳過的——在你開口要程式碼之前,先把 API 文件塞進助手的脈絡裡。今天有三種行之有效的做法:

  • 直接貼上文件頁。message.received 的事件結構和 POST /v1/messages 傳送段落原樣貼進對話框。
  • 掛載文件。 Cursor 的 @Docs、Claude Code 的檔案脈絡、Windsurf 的文件面板——指向 UnifyPort 的介面文件,讓助手按需讀取。
  • 用文件/MCP 連接器(如果你的軟體支援),讓助手自己去拉取文件。

回報很實在。一個真正讀過事件名稱的助手,不會憑空造出一個 onMessage() 處理函式,或者一個根本不存在的 sendText() SDK。它會照著 message.receivedPOST /v1/messages 來寫,因為這就是擺在它面前的東西。餵進去的是垃圾脈絡,吐出來的就是幻覺端點——所以花那兩分鐘,把真東西放進去。

逐條提示詞,把機器人做起來

第一條提示詞——接收端:

用我給你的 UnifyPort webhook 文件,寫一個 Express 伺服器,帶一個 POST /webhook 路由接收標準事件。先做到:當 eventmessage.received 時,印出 fromtext

因為事件結構是固定的,處理邏輯就是對一個欄位做分支判斷:

import express from "express";

const app = express();
app.use(express.json());

app.post("/webhook", (req, res) => {
  const evt = req.body;
  if (evt.event === "message.received") {
    console.log(`${evt.provider} ${evt.from}: ${evt.text}`);
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

第二條提示詞——驗證簽章。 這一步,粗心的實作會跳過,而讀過文件的助手不會:

每次投遞都用 webhook 的 signing_secret 做了 HMAC-SHA256 簽章。在信任請求主體之前先驗證。要用原始請求主體,並做時間恆定的比較。

這裡助手必須做對的一點:HMAC 要算在原始位元組上,而不是重新序列化後的 JSON。只要提示裡寫了「用原始請求主體」,好的助手會接上 express.jsonverify 鉤子來捕捉 rawBody。如果它漏了,這就是你唯一要盯的複查點——而且第一次真實投遞回傳 401 時你就會發現。

第三條提示詞——透過傳送端點回覆:

message.received 到達時,用事件裡的 account_idfrom,透過 POST /v1/messages 回覆傳送者。驗證用環境變數裡的 Bearer API key。

機器人就齊了。助手最終落定的完整檔案:

import express from "express";
import crypto from "crypto";

const { UNIFYPORT_API_KEY, UNIFYPORT_SIGNING_SECRET } = process.env;

const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

function isValid(req) {
  const sig = req.get("x-unifyport-signature") || "";
  const expected = crypto
    .createHmac("sha256", UNIFYPORT_SIGNING_SECRET)
    .update(req.rawBody)
    .digest("hex");
  return sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

app.post("/webhook", async (req, res) => {
  if (!isValid(req)) return res.sendStatus(401);

  const evt = req.body;
  if (evt.event === "message.received") {
    await fetch("https://api.unifyport.ai/v1/messages", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${UNIFYPORT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        account_id: evt.account_id,
        to: evt.from,
        text: "收到囉!您的訊息我們已經收到,稍後會有專人跟進。",
      }),
    });
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

跑起來,看一則訊息進來

連一個帳號(Telegram 最快上手),註冊一個指向你伺服器的 webhook 端點,帶上 signing_secretsubscribed_events: ["message.received"],然後給你的機器人傳一則私訊。事件到達時長這樣:

{
  "event": "message.received",
  "account_id": "acct_8Q2vK",
  "provider": "telegram",
  "from": "user_3f9c1a",
  "text": "請問週末有營業嗎?",
  "timestamp": 1749427200,
  "message_id": "tg_msg_5d2b7e"
}

你的伺服器驗證簽章,呼叫 POST /v1/messages,回覆就出現在對話裡。如果簽章驗證失敗,你會拿到 401——那就是上面那個 rawBody 的坑。修一次,這個迴圈就穩了。

再擴充

正規化 webhook 的價值正是在這裡兌現。加第二個平台,不是再做一次整合——而是新增處理程式碼。連一個 WhatsApp、LINE 或 Zalo 帳號,訂閱同一個 webhook,完全相同的 message.received 分支照樣跑,因為事件結構在不同平台之間不會變。provider 欄位告訴你訊息從哪來,而你程式碼的其餘部分根本不用看。

想讓機器人真的去回答問題,而不只是回一句「收到」?再加一條提示詞就行:「回覆之前,把 text 傳給一個大型語言模型,用它的回覆作為回覆內容。」 傳送呼叫助手已經寫好了,你只是把一個字串換成模型的輸出。

這個下午的啟示,不是「AI 幫你把機器人寫了」。而是:AI 助手的上限,取決於你讓它對接的那個介面。一個事件名稱穩定、只有一個傳送端點的正規化 webhook,正是助手能乾淨收尾的那種介面——一種結構要學,沒有東西要亂編。把你的軟體指向 UnifyPort v1 API,貼進 message.received 的結構,你會在這個下午結束前拿到一個會驗證、會回覆的機器人——然後把它放到其餘五個管道上去。