我將 API 文件丟畀 Claude Code,佢幫我砌咗個 Zalo Webhook 接收器 — UnifyPort
Zalo 冇公開嘅私訊 API。如果你嘅業務面向越南市場——或者任何需要同越南客戶溝通嘅跨境團隊——呢個係你撞到嘅第一堵牆。官方路徑係註冊 Official Account,但嗰個係一套廣播體系:認證審核、訊息模板、互動窗口期、逐條計費。如果你淨係需要接收客戶主動發嚟嘅訊息,OA 解決嘅係一個比你實際需求大得多嘅問題。
所以我換咗個思路。打開 Claude Code,將 UnifyPort 嘅 webhook 參考文件貼入上下文,叫佢用 Python 寫一個 Zalo webhook 接收器。五十分鐘之後,我攞到一個 Flask 服務:驗證每次推送嘅簽章、將 Zalo 訊息寫入結構化 JSON 檔案、同時轉發到 Slack 頻道。成個程式碼大約 60 行。
你最終會攞到咩
一個 Python Flask 服務,功能包括:
- 接收 UnifyPort webhook 推送嘅
message.received事件 - 用 HMAC-SHA256 同你嘅
signing_secret驗證每次推送嘅簽章 - 將每條 Zalo 訊息寫入結構化 JSON 檔案
- 透過 Slack incoming webhook 將訊息摘要推送到 Slack 頻道
時間成本:唔使一個鐘,大部分係 Claude Code 做嘅。你需要一個 UnifyPort 工作空間,入面連接一個 Zalo 帳號(掃 QR code 就得——唔使密碼,唔使企業驗證),同埋一個 Slack incoming webhook URL。
準備工作:將文件放入 Claude Code 嘅上下文
動手之前,先將 API 文件餵畀 agent。Claude Code 可以直接讀取本機檔案,最快嘅方式係:
- 將 webhook 事件參考文件——特別係
message.received事件結構、簽章驗證部分、同埋POST /v1/webhook-endpoints建立介面——複製到專案根目錄嘅unifyport-reference.md檔案入面。 - 或者喺開始對話嗰陣,直接將相關內容貼入聊天窗口。
Claude Code 必須睇到嘅關鍵內容:message.received 事件嘅 payload 結構、x-unifyport-signature 請求標頭、同埋 HMAC-SHA256 配合 signing_secret 嘅驗證方式。少咗呢啲,agent 會猜欄位名——而且一定猜錯。
其他 AI 程式碼工具同理。喺 Cursor 用 @Docs 指向參考文件,喺 Windsurf 用文件面板。核心邏輯一樣:餵入真實嘅欄位名,輸出正確嘅程式碼。
逐步構建,一個 prompt 一個 prompt 嚟
第一個 prompt——骨架:
讀 unifyport-reference.md。用 Flask 寫一個 app.py,包含一個 POST /webhook 路由。當 event 欄位係 “message.received” 時,將 provider、from、text 印到標準輸出。所有事件回傳 200。
Claude Code 讀完參考文件之後產生:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
evt = request.get_json()
if evt.get("event") == "message.received":
print(f"[{evt['provider']}] {evt['from']}: {evt['text']}")
return "", 200
if __name__ == "__main__":
app.run(port=3000)
七行處理邏輯。因為事件結構係統一嘅,淨係需要判斷一個欄位——唔使針對唔同平台做解析。
第二個 prompt——簽章驗證:
每次 webhook 推送都會帶一個 x-unifyport-signature 請求標頭。用環境變數入面嘅 signing_secret,透過 HMAC-SHA256 驗證簽章。HMAC 必須對原始請求主體計算,唔係重新序列化嘅 JSON。驗證失敗回傳 401。用常數時間比較。
呢一步將玩具變成可以上線運行嘅嘢。Claude Code 之所以做得啱,係因為佢讀過參考文件:
import hmac
import hashlib
import os
SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]
def verify_signature(req):
signature = req.headers.get("x-unifyport-signature", "")
expected = hmac.new(
SIGNING_SECRET.encode(),
req.get_data(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
req.get_data() 回傳嘅係 Flask 解析 JSON 之前嘅原始位元組——呢個細節至關重要。如果 agent 用咗 request.get_json() 然後重新序列化,HMAC 永遠對唔上。提示詞入面寫咗「原始請求主體」,佢第一次就寫啱咗。
第三個 prompt——結構化日誌同 Slack 轉發:
加兩個功能:(1) 將每個 message.received 事件追加寫入 messages.jsonl 檔案,每行一個 JSON 物件,包含 timestamp、provider、from、text。(2) 將一行摘要發到環境變數 SLACK_WEBHOOK_URL 指定嘅 Slack incoming webhook。
Claude Code 一次搞掂。完整嘅 app.py:
import hmac
import hashlib
import json
import os
import requests
from flask import Flask, request
app = Flask(__name__)
SIGNING_SECRET = os.environ["UNIFYPORT_SIGNING_SECRET"]
SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL", "")
def verify_signature(req):
signature = req.headers.get("x-unifyport-signature", "")
expected = hmac.new(
SIGNING_SECRET.encode(),
req.get_data(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route("/webhook", methods=["POST"])
def webhook():
if not verify_signature(request):
return "", 401
evt = request.get_json()
if evt.get("event") == "message.received":
record = {
"timestamp": evt["timestamp"],
"provider": evt["provider"],
"from": evt["from"],
"text": evt.get("text", ""),
"message_id": evt["message_id"],
}
with open("messages.jsonl", "a") as f:
f.write(json.dumps(record) + "\n")
if SLACK_WEBHOOK_URL:
requests.post(SLACK_WEBHOOK_URL, json={
"text": f"[{evt['provider']}] {evt['from']}: {evt.get('text', '')}"
})
return "", 200
if __name__ == "__main__":
app.run(port=3000)
六十行程式碼,簽章驗證、結構化日誌、Slack 轉發全部就位。成個 handler 係平台無關嘅——evt["provider"] 話你知呢個係 Zalo 訊息,但程式碼本身從唔判斷平台。
啟動服務,睇一條 Zalo 訊息入嚟
喺 UnifyPort 工作空間入面連接一個 Zalo 帳號——Zalo 用掃碼認證,用 Zalo App 掃一下就連上,幾秒鐘嘅事。唔使企業驗證,唔使註冊 OA,唔使管理任何密碼。註冊一個 webhook 端點,設定 subscribed_events: ["message.received"] 同一個 signing_secret。
啟動服務,然後從一個 Zalo 聯絡人嗰度發一條訊息。事件推送係噉嘅:
{
"event": "message.received",
"account_id": "acct_4Xn9kL",
"provider": "zalo",
"from": "user_7b2e4f",
"text": "Sản phẩm này còn hàng không ạ?",
"timestamp": 1750262400,
"message_id": "zalo_msg_9d3a1c"
}
服務驗證簽章、將記錄追加到 messages.jsonl、將摘要發到 Slack。如果簽章驗證失敗回傳 401——檢查 UNIFYPORT_SIGNING_SECRET 係咪同 webhook 端點設定嘅一致。
擴充:加埋 WhatsApp,唔改一行 handler 程式碼
統一 webhook 嘅價值喺呢度體現。喺同一個工作空間入面連接一個 WhatsApp 帳號,訂閱同一個 webhook 端點,完全一樣嘅 /webhook 路由就可以處理兩個平台。WhatsApp 訊息以相同嘅 message.received 結構到達——provider 欄位唔同,結構完全一樣:
{
"event": "message.received",
"account_id": "acct_2Rm7wQ",
"provider": "whatsapp",
"from": "user_5c8d3a",
"text": "Do you ship to Thailand?",
"timestamp": 1750262460,
"message_id": "wa_msg_6f1b2e"
}
唔使新嘅 handler。唔使條件匯入。唔使 WhatsApp 專屬嘅解析邏輯。messages.jsonl 檔案入面而家同時包含 Zalo 同 WhatsApp 訊息,格式完全一樣,Slack 頻道入面兩個平台嘅訊息同步顯示。之後加 LINE 或 Telegram 都係一樣——連接帳號,已有嘅 handler 全部搞掂。
重點唔係 Claude Code 幫你寫咗伺服器。重點係,當 API 介面簡潔到可以完整放入 agent 上下文嗰陣,AI 程式碼工具就可以一次做啱。一種事件結構、一套簽章機制、一個傳送端點——呢種目標,畀咗 prompt 嘅 agent 唔會搞砸。將 UnifyPort API 參考文件餵畀你嘅工具,畀佢 message.received 嘅 schema,食晏前你就可以攞到一個簽章驗證、日誌記錄、訊息轉發嘅 webhook 接收器——然後唔改程式碼就可以接入其他所有頻道。