我把 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.received 和 POST /v1/messages 來寫,因為這就是擺在它面前的東西。餵進去的是垃圾脈絡,吐出來的就是幻覺端點——所以花那兩分鐘,把真東西放進去。
逐條提示詞,把機器人做起來
第一條提示詞——接收端:
用我給你的 UnifyPort webhook 文件,寫一個 Express 伺服器,帶一個
POST /webhook路由接收標準事件。先做到:當event是message.received時,印出from和text。
因為事件結構是固定的,處理邏輯就是對一個欄位做分支判斷:
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.json 的 verify 鉤子來捕捉 rawBody。如果它漏了,這就是你唯一要盯的複查點——而且第一次真實投遞回傳 401 時你就會發現。
第三條提示詞——透過傳送端點回覆:
當
message.received到達時,用事件裡的account_id和from,透過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_secret 和 subscribed_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 的結構,你會在這個下午結束前拿到一個會驗證、會回覆的機器人——然後把它放到其餘五個管道上去。