ผมให้ Cursor อ่านเอกสาร Webhook แล้วมันสร้าง Relay ส่งข้อความ Telegram ไป Slack ใน 30 นาที — UnifyPort
ถ้าอยากได้ข้อความ Telegram ใน Slack วิธีมาตรฐานคือ: สร้าง bot ผ่าน @BotFather, ตั้งค่า Webhook หรือ getUpdates polling, แยกวิเคราะห์ object Update ที่ซ้อนกันของ Telegram, จัดรูปแบบสำหรับ Slack Incoming Webhook แล้วก็จัดการ rate limit ใช้ได้ แต่โค้ดผูกติดกับ Telegram ทั้งหมด Update object มีโครงสร้างเฉพาะของตัวเอง bot เห็นแค่การสนทนาของตัวมันเอง และถ้าทีหลังอยากรับข้อความ WhatsApp ในช่อง Slack เดียวกัน — ต้องเริ่มต้นใหม่จากศูนย์กับ API ที่ต่างกันโดยสิ้นเชิง
ผมเลือกทางอื่น เปิด Cursor วางเอกสาร Webhook ของ UnifyPort เข้าไป แล้วให้มันสร้าง relay Telegram ไป Slack สามสิบนาทีต่อมาผมได้ Express server ที่ยืนยันลายเซ็นทุก delivery ด้วย HMAC-SHA256 จัดรูปแบบข้อความ Telegram ด้วย Slack Block Kit แล้วส่งไปยังช่อง — ประมาณ 45 บรรทัดโค้ด relay เดียวกันนี้จัดการ WhatsApp, LINE, TikTok, Zalo และ X ได้โดยไม่ต้องแก้โค้ดอะไรเลย เพราะโครงสร้าง event ขาเข้าเหมือนกันหมดทั้ง 6 แพลตฟอร์ม
สิ่งที่จะได้
Node.js Express server ที่ทำได้:
- รับ event
message.receivedจาก Webhook รวมศูนย์ของ UnifyPort - ยืนยันลายเซ็นทุก delivery ด้วย HMAC-SHA256 กับ
signing_secretของคุณ - จัดรูปแบบข้อความแต่ละรายการเป็น Slack Block Kit พร้อมแท็กแพลตฟอร์ม, ผู้ส่ง และ timestamp
- ส่งไปยังช่อง Slack ผ่าน Incoming Webhook
เวลา: ประมาณ 30 นาที ต้องมี workspace UnifyPort ที่เชื่อมต่อบัญชี Telegram แล้ว (สแกน QR — ไม่ต้องสมัคร bot) และ Slack Incoming Webhook URL
ทำไมไม่ใช้ Bot API ตรง ๆ?
ลองดูว่าเส้นทาง Telegram Bot API ต้องทำอะไรบ้าง:
- สร้าง bot ผ่าน @BotFather — ได้ bot token แต่ bot เป็นสิ่งที่แยกจากบัญชีส่วนตัวของคุณ
- เลือกวิธีรับข้อความ:
getUpdatespolling หรือ Webhook — ทั้งคู่ต้องตั้งค่าเฉพาะสำหรับ Telegram - แยก
Updateobject: ข้อความอยู่ที่update.message.textผู้ส่งอยู่ที่update.message.from.idแชทอยู่ที่update.message.chat.id— โครงสร้างเฉพาะของ Telegram - จัดการ rate limit: 30 ข้อความ/วินาทีทั้งระบบ, 20 ข้อความ/นาทีต่อกลุ่ม
- จัดรูปแบบแล้วส่งต่อไป Slack
โค้ดใช้ได้ — จนกว่าคุณจะต้องการข้อความ WhatsApp ในช่องเดียวกัน ตอนนั้นต้องเชื่อมต่อ WhatsApp Cloud API แยก แยกวิเคราะห์ webhook payload อีกแบบ แล้วดูแลสอง code path ที่ทั้งคู่สุดท้ายก็ส่งออกเป็น Slack message สามแพลตฟอร์มก็สาม code path
Webhook ของ UnifyPort ยุบทั้งหมดเข้าด้วยกัน ทุกแพลตฟอร์มส่ง event message.received เหมือนกัน — provider, from, text, message_id handler เดียวรับได้ทั้ง 6 ช่อง
เตรียมตัว: ส่งเอกสารให้ Cursor
ก่อนเขียน prompt ให้ใส่ API reference เข้า context ของ Cursor ใช้ @Docs เพิ่มเอกสาร Webhook ของ UnifyPort — โดยเฉพาะ:
- โครงสร้าง payload ของ event
message.received - header
x-unifyport-signatureและส่วนอธิบายการยืนยัน HMAC-SHA256 - การเรียก
POST /v1/webhook-endpointsเพื่อสร้าง endpoint
หรือวางส่วนเหล่านั้นลงในแชทโดยตรง สิ่งสำคัญคือ: ชื่อ field จริงใส่เข้าไป โค้ดถูกต้องออกมา ถ้าไม่มีเอกสาร Cursor จะแต่ง callback telegram.onMessage() หรือ handler bot.on('text') ขึ้นมา — ซึ่งไม่มีอยู่ใน API ของ UnifyPort
Claude Code, Windsurf, Copilot ใช้ได้เหมือนกัน — file context, docs panel, หรือวางลงในแชท เครื่องมือเป็นรอง เอกสารเป็นหลัก
การ build
Prompt แรก — โครง relay:
ใช้เอกสาร UnifyPort webhook เขียน Express server ที่มี route POST /webhook เมื่อ event เป็น “message.received” ให้ส่งข้อความที่จัดรูปแบบแล้วไปยัง Slack incoming webhook จาก SLACK_WEBHOOK_URL ใส่ชื่อ provider, ผู้ส่ง และข้อความ คืน 200 สำหรับทุก event
Cursor อ่านเอกสารแล้วสร้าง:
import express from "express";
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL;
const app = express();
app.use(express.json());
app.post("/webhook", async (req, res) => {
const evt = req.body;
if (evt.event === "message.received") {
await fetch(SLACK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `*[${evt.provider}]* ${evt.from}: ${evt.text}`,
}),
});
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
สิบห้าบรรทัดของ handler เพราะโครงสร้าง event เหมือนกันทั้ง 6 แพลตฟอร์ม ไม่ต้องแยกวิเคราะห์อะไรเฉพาะของ Telegram — evt.provider มีค่า "telegram" แต่โครงสร้างเหมือนกับข้อความจาก WhatsApp หรือ LINE ทุกประการ
Prompt ที่สอง — ยืนยันลายเซ็น:
แต่ละ delivery มี header x-unifyport-signature ยืนยันด้วย HMAC-SHA256 โดยใช้ signing_secret จาก environment variable HMAC ต้องคำนวณจาก raw request body — ไม่ใช่ JSON ที่ re-serialize ใหม่ คืน 401 ถ้าไม่ผ่าน ใช้ timing-safe comparison
Cursor เพิ่มชั้นยืนยัน:
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 ถ้า Cursor ใช้ req.body แล้ว re-serialize ความแตกต่างของ whitespace จะทำให้ HMAC ไม่มีวัน match prompt บอก “raw request body” จึงทำถูกตั้งแต่ครั้งแรก
Prompt ที่สาม — Slack format ที่สวยขึ้น:
เปลี่ยน Slack message เป็น Block Kit แสดง provider เป็นแท็ก, sender ID ตัวหนา เพิ่ม context block แสดง timestamp กับ message_id
server.js ฉบับสมบูรณ์:
import express from "express";
import crypto from "crypto";
const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
const SLACK_WEBHOOK = process.env.SLACK_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" && SLACK_WEBHOOK) {
await fetch(SLACK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*[${evt.provider}]* from *${evt.from}*\n${evt.text}`,
},
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `${evt.message_id} · ${new Date(evt.timestamp * 1000).toISOString()}`,
},
],
},
],
}),
});
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
ไม่ถึง 50 บรรทัด ยืนยันลายเซ็น, จัดรูปแบบ Slack Block Kit, ส่งต่อข้อความขาเข้าทั้งหมด handler ไม่เคยตรวจค่า provider เพราะไม่จำเป็น
รันแล้วดูข้อความ Telegram เข้ามา
เชื่อมต่อบัญชี Telegram ใน workspace UnifyPort — Telegram รองรับยืนยันตัวตนด้วย QR สแกนจากแอป Telegram แค่ไม่กี่วินาทีก็เสร็จ ไม่ต้อง @BotFather ไม่ต้อง bot token ไม่ต้องจัดการ API credential ที่เชื่อมต่อคือบัญชี Telegram จริงของคุณ ไม่ใช่ bot แยก
ลงทะเบียน webhook endpoint ชี้ไปที่ server ตั้ง subscribed_events: ["message.received"] กับ signing_secret เริ่ม server แล้วส่งข้อความทดสอบไปยังบัญชี Telegram ของคุณจาก user อื่น event มาถึง:
{
"event": "message.received",
"account_id": "acct_5Qm8nR",
"provider": "telegram",
"from": "user_7c3d9e",
"text": "พรุ่งนี้บ่ายสามยังนัดกันอยู่ไหม?",
"timestamp": 1750521600,
"message_id": "tg_msg_2a6f4b"
}
server ยืนยันลายเซ็น จัดรูปแบบ Block Kit payload แล้วส่งไป Slack ข้อความปรากฏในช่องพร้อมแท็กแพลตฟอร์ม ผู้ส่ง timestamp และเนื้อหาเต็ม ถ้ายืนยันลายเซ็นไม่ผ่านได้ 401 — ตรวจว่า UNIFYPORT_SIGNING_SECRET ตรงกับค่าที่ตั้งไว้บน webhook endpoint
ประโยชน์ด้านสถาปัตยกรรม: เพิ่ม WhatsApp ไม่ต้องแก้โค้ด
ตรงนี้คือจุดที่ relay นี้แตกต่างจาก Bot API build เชื่อมต่อบัญชี WhatsApp ใน workspace เดียวกัน subscribe webhook endpoint เดียวกัน ข้อความ WhatsApp มาในโครงสร้างเดียวกันทุกประการ:
{
"event": "message.received",
"account_id": "acct_3Xk1wL",
"provider": "whatsapp",
"from": "user_9b4e7a",
"text": "ส่งใบแจ้งหนี้แล้วนะ ช่วยยืนยันการรับด้วย",
"timestamp": 1750521660,
"message_id": "wa_msg_8d2c5f"
}
handler เดียวกัน ช่อง Slack เดียวกัน แท็ก [whatsapp] แยกจาก [telegram] ใน Slack แต่ code path เหมือนกันหมด ถ้าเป็น Telegram Bot API relay ต้องทำอีกหนึ่ง integration แยก — webhook payload ต่าง, logic แยกวิเคราะห์ต่าง, จัดการ error ต่าง relay นี้จัดการ LINE, TikTok, Zalo และ X ด้วยวิธีเดียวกัน เพราะโครงสร้าง event ไม่เปลี่ยนระหว่างแพลตฟอร์ม
นี่คือความแตกต่างระหว่าง build บน API ของแพลตฟอร์มเดียว กับ build บนชั้น normalize Bot API ผูก relay ไว้กับ Telegram เอกสาร Webhook ของ UnifyPort ให้ Cursor — หรือ AI coding tool ตัวไหนก็ได้ — พื้นผิวที่เรียบง่ายพอจะทำเสร็จในครั้งเดียว แล้วผลลัพธ์ใช้ได้กับทุกช่องทางที่คุณเชื่อมต่อทีหลัง ป้อน เอกสาร API ของ UnifyPort ให้เครื่องมือของคุณ วาง schema message.received เข้าไป แล้ว relay จะพร้อมใช้ก่อนมื้อเที่ยง