ผมให้ Claude Code สร้างตัวมอนิเตอร์ DM และ Mention บน X — โดยไม่แตะ API ของ X เลย — UnifyPort
API ของ X ไม่มีแพ็กเกจฟรี การอ่าน DM หนึ่งข้อความมีค่าใช้จ่าย $0.015 การอ่านโพสต์หนึ่งรายการ $0.005 ถ้าคุณเป็นทีมสองคนที่แค่อยากรู้ว่ามีคนส่ง DM หรือ mention คุณบน X เมื่อไหร่ แพลตฟอร์มนักพัฒนาอย่างเป็นทางการจะขอให้คุณเติมเงิน สร้างโปรเจกต์ รอการอนุมัติ และเริ่มคิดค่าใช้จ่ายตามรายการ — ก่อนที่คุณจะประมวลผลแม้แต่หนึ่ง event
ผมข้ามทั้งหมดนั้น เปิด Claude Code วางเอกสาร webhook ของ UnifyPort เข้าไปใน context แล้วขอให้มันสร้างตัวมอนิเตอร์ DM และ mention ของ X ด้วย Node.js สี่สิบนาทีต่อมา ผมได้ Express server ที่ตรวจสอบลายเซ็นทุกครั้งที่มีการส่งข้อมูล เขียนแต่ละ event ลง structured log file และส่งการแจ้งเตือนไปยัง Discord channel ไม่ต้องมีบัญชีนักพัฒนา X ไม่มีค่าใช้จ่ายตามรายการ ไม่ต้องรอคิวอนุมัติ
สิ่งที่คุณจะได้
Node.js Express server ที่:
- รับ event
message.receivedจาก unified webhook ของ UnifyPort - ตรวจสอบทุกการส่งข้อมูลด้วย HMAC-SHA256 กับ
signing_secret - เขียนข้อความ X แต่ละรายการลงไฟล์
messages.jsonl - ส่งการแจ้งเตือนไปยัง Discord channel ผ่าน webhook
เวลา: ไม่ถึงหนึ่งชั่วโมง คุณต้องมี workspace ของ UnifyPort ที่เชื่อมต่อบัญชี X ผ่านส่วนขยายเบราว์เซอร์ UnifyPort Exporter — นำเข้า session ด้วยคลิกเดียว ไม่ต้องใช้ข้อมูลรับรองนักพัฒนา — และ Discord webhook URL
เตรียมพร้อม: ป้อนเอกสารเข้า context ของ Claude Code
ก่อน prompt ให้ป้อนเอกสาร API เข้าไปใน context ของ agent ก่อน Claude Code อ่านไฟล์ได้โดยตรง ให้สร้าง unifyport-reference.md ที่ root ของโปรเจกต์ โดยมี:
- โครงสร้าง payload ของ event
message.received - header
x-unifyport-signatureและคำอธิบายการตรวจสอบ HMAC-SHA256 - การเรียก
POST /v1/webhook-endpoints
หรือวางส่วนเหล่านั้นลงในการสนทนาโดยตรง จุดสำคัญ: ชื่อ field จริงเข้าไป โค้ดที่ถูกต้องออกมา ถ้าไม่มีเอกสาร agent จะสร้าง SDK เฉพาะ X หรือประเภท event dm.received ที่ไม่มีอยู่จริงขึ้นมาเอง
Cursor, Windsurf, Copilot ใช้วิธีเดียวกัน — ใช้ @Docs แผงเอกสาร หรือวางเอกสารเข้า context เครื่องมือเป็นเรื่องรอง เอกสารคือหัวใจ
กระบวนการสร้าง ทีละ prompt
Prompt แรก — โครงสร้างหลัก:
อ่าน unifyport-reference.md เขียน Express server ใน server.js พร้อม route POST /webhook เมื่อ event เป็น “message.received” ให้ log ค่า provider, from และ text คืน 200 สำหรับทุก request
Claude Code สร้าง:
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"));
handler หกบรรทัด เพราะโครงสร้าง event เหมือนกันทั้งหกแพลตฟอร์ม ไม่ต้อง parse อะไรเฉพาะสำหรับ X — evt.provider แสดง "x" แต่โครงสร้างเหมือนกับข้อความ WhatsApp หรือ Telegram ทุกประการ
Prompt ที่สอง — ตรวจสอบลายเซ็น:
ทุกครั้งที่ส่งข้อมูลจะมี header x-unifyport-signature ตรวจสอบด้วย HMAC-SHA256 โดยใช้ signing_secret จากตัวแปรสภาพแวดล้อม HMAC ต้องคำนวณจาก raw request body ไม่ใช่ JSON ที่ serialize ใหม่ ถ้าไม่ผ่านให้คืน 401 ใช้การเปรียบเทียบแบบ timing-safe
Claude Code เพิ่มชั้นตรวจสอบ:
import crypto from "crypto";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
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", SIGNING_SECRET)
.update(req.rawBody)
.digest("hex");
return sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
callback verify จับ raw buffer ก่อนที่ Express จะ parse JSON — ถ้า agent ใช้ req.body แล้ว serialize กลับเป็น JSON HMAC จะไม่มีวันตรง เพราะ prompt ระบุว่า “raw request body” agent จึงเขียนถูกตั้งแต่ครั้งแรก
Prompt ที่สาม — logging และแจ้งเตือน Discord:
เพิ่มสองฟีเจอร์: (1) เขียนแต่ละ event message.received ต่อท้ายไฟล์ messages.jsonl — หนึ่ง JSON object ต่อบรรทัด มี timestamp, provider, from, text และ message_id (2) ส่งการแจ้งเตือนหนึ่งบรรทัดไปยัง Discord webhook URL จากตัวแปรสภาพแวดล้อม DISCORD_WEBHOOK_URL
server.js ฉบับสมบูรณ์:
import express from "express";
import crypto from "crypto";
import { appendFileSync } from "fs";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK_URL;
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", 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") {
const record = JSON.stringify({
timestamp: evt.timestamp,
provider: evt.provider,
from: evt.from,
text: evt.text,
message_id: evt.message_id,
});
appendFileSync("messages.jsonl", record + "\n");
if (DISCORD_WEBHOOK) {
await fetch(DISCORD_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: `**[${evt.provider}]** ${evt.from}: ${evt.text}`,
}),
});
}
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
ไม่ถึง 50 บรรทัด ตรวจสอบลายเซ็น, log ลงดิสก์, ส่งต่อ Discord — พร้อมทุกอย่าง handler ไม่แยก branch ตาม provider เพราะโครงสร้าง event เหมือนกัน ไม่ว่าข้อความจะมาจาก X, WhatsApp หรือแพลตฟอร์มอื่น
เรียกใช้แล้วดูข้อความ X มาถึง
เชื่อมต่อบัญชี X ในแดชบอร์ด UnifyPort สำหรับ X ใช้การนำเข้า session ผ่านส่วนขยายเบราว์เซอร์ UnifyPort Exporter — คลิกไอคอนส่วนขยาย ให้สิทธิ์ บัญชีเชื่อมต่อภายในไม่กี่วินาที ไม่ต้องมีพอร์ทัลนักพัฒนา ไม่ต้องสมัคร API key ไม่ต้องรอคิวอนุมัติ
ลงทะเบียน webhook endpoint ชี้ไปที่ server ของคุณ ตั้ง subscribed_events: ["message.received"] และ signing_secret เริ่ม server แล้วส่ง DM ทดสอบจากบัญชีอื่นมายังบัญชี X ของคุณ event มาถึง:
{
"event": "message.received",
"account_id": "acct_7Kp3mR",
"provider": "x",
"from": "user_9d4f2b",
"text": "Hey, are you still taking freelance projects?",
"timestamp": 1750521600,
"message_id": "x_msg_8c1e5a"
}
server ตรวจสอบลายเซ็น เขียนรายการลง messages.jsonl และส่งการแจ้งเตือนไป Discord ถ้าตรวจลายเซ็นไม่ผ่าน — 401 — ตรวจสอบว่า UNIFYPORT_SIGNING_SECRET ตรงกับค่าที่ตั้งไว้บน webhook endpoint
ขยาย: เพิ่ม Telegram โดยไม่ต้องแก้โค้ด
เชื่อมต่อบัญชี Telegram ใน workspace เดียวกัน subscribe webhook endpoint เดิม ข้อความ Telegram มาถึงในโครงสร้างเดียวกันทุกประการ — field provider ต่างกัน แต่โครงสร้างเหมือนกัน:
{
"event": "message.received",
"account_id": "acct_2Xn5wL",
"provider": "telegram",
"from": "user_4a7c8d",
"text": "Can you hop on a call tomorrow?",
"timestamp": 1750521660,
"message_id": "tg_msg_3b9f1e"
}
ไม่ต้องมี handler ใหม่ ไม่ต้อง parse เฉพาะ Telegram ไฟล์ messages.jsonl ตอนนี้มีทั้ง event ของ X และ Telegram ในสคีมาเดียวกัน Discord แสดงการแจ้งเตือนจากทั้งสองสตรีม การเพิ่ม WhatsApp, LINE, Zalo หรือ TikTok ในภายหลังก็เป็นกระบวนการเดียวกัน — เชื่อมต่อบัญชี handler ที่สร้างไว้แล้วจะจัดการทุกอย่าง
สำหรับทีมในประเทศไทยที่ใช้ LINE เป็นหลัก นี่เป็นจุดที่น่าสนใจเป็นพิเศษ — เพิ่ม LINE เข้าไปใน webhook endpoint เดียวกัน แล้วข้อความ LINE จะไหลเข้า messages.jsonl และ Discord เคียงข้างข้อความ X ทั้งหมดด้วย schema เดียวกัน
ความแตกต่างระหว่างวิธีนี้กับการใช้ API ของ X โดยตรงไม่ใช่แค่ค่าใช้จ่าย — แต่เป็นสิทธิ์การเข้าถึง X ต้องการบัญชีนักพัฒนาที่ผ่านการอนุมัติ โปรเจกต์ที่เติมเงินแล้ว และสัญญาจ่ายค่าอ่านตามรายการ เส้นทางนำเข้า session ของ UnifyPort เชื่อมต่อบัญชี X ส่วนตัวของคุณผ่านเบราว์เซอร์ และทำให้ทุก DM และ mention เป็นโครงสร้าง event เดียวกับอีกห้าช่องทาง ชี้ Claude Code ไปที่อินเทอร์เฟซนี้ — event schema เดียว ระบบลายเซ็นเดียว — แล้วคุณจะมีตัว listener ที่ตรวจสอบ, log, แจ้งเตือนพร้อมทำงานก่อนมื้อเที่ยง จากนั้นเพิ่มทุกช่องทางอื่นโดยไม่ต้องแตะ handler