Skip to content

การตรวจสอบลายเซ็น (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. ขั้นตอนการตรวจสอบ

สิ่งที่ต้องเตรียมก่อนตรวจสอบ:

  1. Raw HTTP Request Body (ห้าม parse JSON ก่อนเด็ดขาด!)
  2. 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:

javascript
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)

python
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)

Released under the MIT License.