การตรวจสอบลายเซ็น (Signature Verification)
เพื่อป้องกัน Spoofing Attack (การปลอมตัวเป็น RawPush แล้วส่ง Request ปลอมเข้ามาที่ Webhook Endpoint ของคุณ)
คุณจำเป็นต้องเช็ค signature ด้วย HMAC-SHA256 ที่ RawPush แนบมาใน HTTP Headers ของทุก request ที่ส่งออกไป!
🔒 1. ตำแหน่งที่ตั้งของลายเซ็น
RawPush จะแนบ Signature มากับทุก Request ใน HTTP Header (รูปแบบ HEX string):
- Header Name:
X-Webhook-Signature
2. ขั้นตอนการตรวจสอบ
สิ่งที่ต้องเตรียมก่อนตรวจสอบ:
- Raw HTTP Request Body (ห้าม parse JSON ก่อนเด็ดขาด!)
Webhook Secret(ขึ้นต้นด้วยwhsec_...copy จากแท็บ Settings ใน Dashboard)
สมการเปรียบเทียบ:
HMAC-SHA256("raw request body", "Webhook Secret") = "signature 64 ตัวอักษร"
ถ้านำ Raw Body มา sign ด้วย Webhook Secret ผ่านอัลกอริทึม HMAC-SHA256 แล้ว ค่า Hex 64 หลักที่ได้ ต้องตรงกับค่าใน Header เสมอ
💻 3. ตัวอย่างการเขียนโค้ดตรวจสอบลายเซ็น
นี่คือตัวอย่างในแต่ละภาษา ที่ควรประยุกต์ดักเป็น Middleware หน้าบ้านที่สุดของการรับ Webhook API
ตัวอย่าง: Node.js (Express)
ข้อควรระวังของ Node.js คือ express.json() จะ parse Body อัตโนมัติ ทำให้ค่า Signature ไม่ตรงกัน ต้องใช้ Raw Body (Buffer) เฉพาะ Endpoint ที่รับ Webhook:
import express from 'express';
import crypto from 'crypto';
const app = express();
const WEBHOOK_SECRET = 'whsec_your_secret_from_dashboard';
// ใช้ express.raw แทน express.json เพื่อเข้าถึง Raw Buffer ได้
app.post(
'/api/webhook/rawpush',
express.raw({ type: 'application/json' }), // <--- สำคัญมาก!!
(req, res) => {
// 1. ดึง Signature จาก Header
const rawSignature = req.headers['x-webhook-signature'];
// 2. ถ้าไม่มี Signature ใน Header — ปฏิเสธทันที
if (!rawSignature) {
return res.status(401).send('Missing Signature Header');
}
try {
// 3. คำนวณ HMAC-SHA256 จาก Raw Body + Webhook Secret
const computedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body) // req.body ตอนนี้เป็น Buffer สด (Raw)
.digest('hex');
// 4. เทียบ Signature!
if (computedSignature !== rawSignature) {
console.error("❌ signature ไม่ตรงกัน! อาจโดนปลอมแปลง");
return res.status(401).send('Invalid Signature');
}
console.log("✅ signature ตรงกัน 100%! มาจาก RawPush แน่นอน");
// Signature ตรงกัน ต่อไป parse JSON ได้เลย
const payload = JSON.parse(req.body.toString());
// saveToDB(payload);
res.status(200).send({ received: true });
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`);
}
}
);
app.listen(3000, () => console.log('Listening on port 3000'));ตัวอย่าง: Python (Flask)
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_from_dashboard"
@app.route('/api/webhook/rawpush', methods=['POST'])
def handle_webhook():
# 1. ดึง Signature จาก Header
raw_signature = request.headers.get('X-Webhook-Signature')
if not raw_signature:
return jsonify({"error": "Missing signature"}), 401
# 2. ดึง Raw Body
raw_body = request.get_data()
# 3. คำนวณ HMAC-SHA256
computed_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
msg=raw_body,
digestmod=hashlib.sha256
).hexdigest()
# 4. เทียบ Signature!
if not hmac.compare_digest(computed_signature, raw_signature):
return jsonify({"error": "Invalid signature"}), 401
print("✅ signature ตรงกัน 100%! มาจาก RawPush แน่นอน")
payload = json.loads(raw_body)
# save_to_db(payload)
return jsonify({"received": True}), 200
if __name__ == '__main__':
app.run(port=3000)💡 อย่าลืมอัปเดตหลัง Revoke
ตัวแปร Webhook Secret (whsec_...) สามารถถูก Revoke/Rotate ได้เช่นเดียวกับ API Keys ใน Dashboard เมื่อคุณสร้าง Secret ใหม่ อย่าลืมเปลี่ยนค่าที่ฝั่ง Backend ด้วย ไม่งั้นทุก Request จาก RawPush จะโดนปฏิเสธ (401 Invalid Signature)
