บริการภายนอกเรียก localhost โดยตรงไม่ได้ tunnel จะสร้าง URL HTTPS สาธารณะและส่งต่อ headers กับ body ต้นฉบับมายังเซิร์ฟเวอร์ในเครื่อง เก็บ raw body ก่อน JSON parser ตรวจสอบลายเซ็นและข้อมูล บันทึกการส่งลง storage แบบถาวร แล้วจึงเริ่ม business logic
เส้นทางของ Linear webhook มายังแอปในเครื่อง
บริการภายนอกเรียก localhost โดยตรงไม่ได้ tunnel จะสร้าง URL HTTPS สาธารณะและส่งต่อ headers กับ body ต้นฉบับมายังเซิร์ฟเวอร์ในเครื่อง >เอกสารอย่างเป็นทางการ
1. สร้าง endpoint ที่เก็บ raw body
เก็บ raw body ก่อน JSON parser ตรวจสอบลายเซ็นและข้อมูล บันทึกการส่งลง storage แบบถาวร แล้วจึงเริ่ม business logic
import express from "express";
import crypto from "node:crypto";
const app = express();
const secret = process.env.LINEAR_WEBHOOK_SECRET;
app.post(
"/webhooks/linear",
express.raw({ type: "application/json", limit: "1mb" }),
async (req, res) => {
const rawBody = req.body;
const signature = req.get("linear-signature");
if (!verifyLinearSignature(signature, rawBody, secret)) {
return res.sendStatus(401);
}
let payload;
try {
payload = JSON.parse(rawBody.toString("utf8"));
} catch {
return res.sendStatus(400);
}
if (!Number.isFinite(payload.webhookTimestamp) ||
Math.abs(Date.now() - payload.webhookTimestamp) > 60_000) {
return res.sendStatus(401);
}
const deliveryId = req.get("linear-delivery") || payload.webhookId;
if (!deliveryId) return res.sendStatus(400);
try {
await recordAndEnqueueOnce(deliveryId, payload);
return res.sendStatus(200);
} catch (error) {
console.error("Linear webhook persistence failed", error);
return res.sendStatus(500);
}
}
);
app.use(express.json());
app.listen(3000);
เก็บ raw body ก่อน JSON parser ตรวจสอบลายเซ็นและข้อมูล บันทึกการส่งลง storage แบบถาวร แล้วจึงเริ่ม business logic >เอกสารอย่างเป็นทางการ
2. เปิดพอร์ตในเครื่องผ่าน HTTPS
เปิดเซิร์ฟเวอร์และรัน tunnel ใน terminal อีกหน้าตลอดการทดสอบ การได้ 401 จาก request ที่ไม่มีลายเซ็นแสดงว่า routing ทำงาน
npx portpreview 3000
https://example.portpreview.dev/webhooks/linear
เปิดเซิร์ฟเวอร์และรัน tunnel ใน terminal อีกหน้าตลอดการทดสอบ การได้ 401 จาก request ที่ไม่มีลายเซ็นแสดงว่า routing ทำงาน
3. ตั้งค่า webhook ใน Linear
บันทึก URL HTTPS แบบเต็ม เลือกเฉพาะ event ที่ต้องใช้ และสร้าง event จริงใน workspace ทดสอบ เก็บ secret นอก repository
บันทึก URL HTTPS แบบเต็ม เลือกเฉพาะ event ที่ต้องใช้ และสร้าง event จริงใน workspace ทดสอบ เก็บ secret นอก repository
ตรวจสอบลายเซ็นจากไบต์ต้นฉบับ
คำนวณ HMAC ด้วย webhook secret และข้อมูลต้นฉบับตามสเปก เปรียบเทียบแบบ constant-time และห้ามเขียน secret ลง log
function verifyLinearSignature(signature, rawBody, secret) {
if (!secret || typeof signature !== "string" ||
!/^[0-9a-f]{64}$/i.test(signature)) {
return false;
}
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest();
const actual = Buffer.from(signature, "hex");
return actual.length === expected.length &&
crypto.timingSafeEqual(actual, expected);
}
คำนวณ HMAC ด้วย webhook secret และข้อมูลต้นฉบับตามสเปก เปรียบเทียบแบบ constant-time และห้ามเขียน secret ลง log >คู่มือฉบับเต็ม
ป้องกัน replay ด้วย timestamp
ตรวจสอบความใหม่ของ timestamp หลังลายเซ็นผ่าน และตั้งเวลาเครื่องให้ตรงเพื่อไม่ให้ request ที่ถูกต้องถูกปฏิเสธ
ตรวจสอบความใหม่ของ timestamp หลังลายเซ็นผ่าน และตั้งเวลาเครื่องให้ตรงเพื่อไม่ให้ request ที่ถูกต้องถูกปฏิเสธ
ทำความเข้าใจ payload และรหัสการส่ง
รองรับเฉพาะ type ที่รู้จัก ยอมรับ field ใหม่ และใช้รหัสการส่งที่คงที่เพื่อ deduplication ใน storage
รองรับเฉพาะ type ที่รู้จัก ยอมรับ field ใหม่ และใช้รหัสการส่งที่คงที่เพื่อ deduplication ใน storage
ตอบกลับเร็วและประมวลผลแบบ idempotent
จองรหัสการส่งและสร้าง job ใน transaction เดียวก่อนตอบ 200 หากบันทึกถาวรไม่ได้ให้ตอบ error เพื่อรอ retry
จองรหัสการส่งและสร้าง job ใน transaction เดียวก่อนตอบ 200 หากบันทึกถาวรไม่ได้ให้ตอบ error เพื่อรอ retry >คู่มือฉบับเต็ม
ทดสอบขั้นตอนทั้งหมดในเครื่อง
- เปิดเซิร์ฟเวอร์และรัน tunnel ใน terminal อีกหน้าตลอดการทดสอบ การได้ 401 จาก request ที่ไม่มีลายเซ็นแสดงว่า routing ทำงาน
- บันทึก URL HTTPS แบบเต็ม เลือกเฉพาะ event ที่ต้องใช้ และสร้าง event จริงใน workspace ทดสอบ เก็บ secret นอก repository
- คำนวณ HMAC ด้วย webhook secret และข้อมูลต้นฉบับตามสเปก เปรียบเทียบแบบ constant-time และห้ามเขียน secret ลง log
- จองรหัสการส่งและสร้าง job ใน transaction เดียวก่อนตอบ 200 หากบันทึกถาวรไม่ได้ให้ตอบ error เพื่อรอ retry
ตรวจ routing, headers, ลายเซ็น เวลาตอบกลับ และ idempotency โดยใช้ request จำลองสำหรับทดสอบเส้นทางปฏิเสธ
แก้ปัญหา Linear webhook
- เปิดเซิร์ฟเวอร์และรัน tunnel ใน terminal อีกหน้าตลอดการทดสอบ การได้ 401 จาก request ที่ไม่มีลายเซ็นแสดงว่า routing ทำงาน
- คำนวณ HMAC ด้วย webhook secret และข้อมูลต้นฉบับตามสเปก เปรียบเทียบแบบ constant-time และห้ามเขียน secret ลง log
- ตรวจสอบความใหม่ของ timestamp หลังลายเซ็นผ่าน และตั้งเวลาเครื่องให้ตรงเพื่อไม่ให้ request ที่ถูกต้องถูกปฏิเสธ
- ตรวจ POST path, port ของ tunnel, raw body, secret, เวลาเครื่อง และ latency ของฐานข้อมูล
รายการตรวจสอบความปลอดภัยสำหรับเครื่องพัฒนาและ production
- บังคับ HTTPS จำกัดขนาดและ method ป้องกัน secret ปกปิดข้อมูลใน log และลบ URL ทดสอบที่หมดอายุ
- เก็บ raw body ก่อน JSON parser ตรวจสอบลายเซ็นและข้อมูล บันทึกการส่งลง storage แบบถาวร แล้วจึงเริ่ม business logic
- จองรหัสการส่งและสร้าง job ใน transaction เดียวก่อนตอบ 200 หากบันทึกถาวรไม่ได้ให้ตอบ error เพื่อรอ retry
บังคับ HTTPS จำกัดขนาดและ method ป้องกัน secret ปกปิดข้อมูลใน log และลบ URL ทดสอบที่หมดอายุ >คู่มือฉบับเต็ม
