← บทความทั้งหมด
บทช่วยสอน

ผมให้ 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 ที่:

  1. รับ event message.received จาก unified webhook ของ UnifyPort
  2. ตรวจสอบทุกการส่งข้อมูลด้วย HMAC-SHA256 กับ signing_secret
  3. เขียนข้อความ X แต่ละรายการลงไฟล์ messages.jsonl
  4. ส่งการแจ้งเตือนไปยัง 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