WebHooks
หัวข้อทั้งหมดในหน้านี้
Webhooks
Webhook คืออะไร
Webhook เป็นการแจ้งเตือนจากเซิร์ฟเวอร์ของเราไปยัง HTTPS endpoint ที่คุณกำหนด เมื่อเกิดเหตุการณ์ (Event) บางอย่างในบัญชีของคุณ โดยแต่ละการแจ้งเตือนจะส่งออบเจ็กต์ event ที่มีข้อมูลของเหตุการณ์นั้นไปยัง Endpoint URL
Webhook ช่วยให้ร้านค้าได้รับการแจ้งเตือนแบบเรียลไทม์ เมื่อเกิดเหตุการณ์สำคัญในบัญชี โดยไม่ต้องเรียก API ซ้ำๆ เพื่อตรวจสอบสถานะ เพราะ Webhook จะส่งการแจ้งเตือนไปยังเซิร์ฟเวอร์ทันทีเมื่อมีเหตุการณ์เกิดขึ้น
ประโยชน์ของ Webhook
- ระบบทำงานมีประสิทธิภาพมากขึ้น: ร้านค้าไม่จำเป็นต้องเรียก API ซ้ำๆ ช่วยลดภาระของเซิร์ฟเวอร์และคำขอที่ไม่จำเป็น
- อัปเดตแบบเรียลไทม์: รับการแจ้งเตือนทันทีเมื่อเกิดเหตุการณ์ เช่น การชำระเงินสำเร็จ การคืนเงิน หรือข้อโต้แย้ง
- รองรับระบบ Automation: Webhook สามารถทริกเกอร์เวิร์กโฟลว์อัตโนมัติในแอปพลิเคชันของร้านค้าทันที เช่น อัปเดตสถานะคำสั่งซื้อ ส่งอีเมลให้ลูกค้า หรือปรับยอดบัญชี
- ความน่าเชื่อถือสูง: ช่วยให้ร้านค้าไม่พลาดเหตุการณ์สำคัญ แม้ในช่วงที่แอปพลิเคชันของร้านค้าไม่ได้ตรวจสอบสถานะอย่างต่อเนื่อง
การกำหนดค่า
ร้านค้าสามารถกำหนดค่า Webhook endpoints แยกสำหรับโหมดทดสอบและโหมดใช้งานจริงได้
- โหมดทดสอบ (Test mode): https://dashboard.omise.co/test/webhooks
- โหมดใช้งานจริง (Live mode): https://dashboard.omise.co/live/webhooks
ดูข้อมูล Event ทั้งหมด
ร้านค้าสามารถเข้าถึงข้อมูล Event ทั้งหมดได้ผ่าน:
- หน้า Events บนแดชบอร์ด (คลิกไอคอนบัญชีที่แถบด้านขวา แล้วเลือก Events)
- The Events API
การประมวลผลเป็นลำดับ (Serialization)
ออบเจ็กต์ Event ในคำขอ POST (หรือการตอบกลับผ่าน Events API) จะประมวลผลตามลำดับตาม API Version เริ่มต้นของ Event เสมอ และไม่ขึ้นกับค่า Omise-Version ที่ส่งไปกับคำขอที่ทริกเกอร์ Event
ตัวอย่าง
สำหรับบัญชีตั้งค่าเวอร์ชันไว้ที่ 2017-11-02:
- รายการรับชำระเงิน (Charge) ถูกสร้างโดยใช้
curlที่ข้อมูลเวอร์ชัน-H "Omise-Version: 2019-05-29" - คำขอนี้จะสร้าง Event
charge.createซึ่งฝังออบเจ็กต์Chargeไว้ภายใน - ออบเจ็กต์ดังกล่าวจะเรียงลำดับตาม Charge API เวอร์ชัน
2017-11-02ไม่ใช่เวอร์ชัน2019-05-29 - แม้จะอัปเดตบัญชีเป็นเวอร์ชัน
2019-05-29ในภายหลัง ออบเจ็กต์ Charge สำหรับ Event นี้จะยังคงเรียงลำดับตามเวอร์ชัน2017-11-02
ข้อกำหนดและแนวทางปฏิบัติ
ข้อกำหนด
- URL ต้องเป็น HTTPS
- URL ต้องมีใบรับรอง SSL ที่ถูกต้อง (ไม่รองรับใบรับรองที่สร้างขึ้นเอง)
- สามารถตรวจสอบใบรับรอง SSL ของคุณได้โดยใช้ SSL Labs
- สำหรับใบรับรอง SSL แบบไม่มีค่าใช้จ่าย สามารถดูรายละเอียดเพิ่มเติมได้ที่ Let's Encrypt
แนวทางปฏิบัติด้านความปลอดภัย
แนะนำ: การตรวจสอบลายเซ็น (Signature Verification)
เราขอแนะนำให้ใช้การตรวจสอบลายเซ็นเป็นมาตรการรักษาความปลอดภัยหลักเพื่อยืนยันความถูกต้องของ Webhook การตรวจสอบลายเซ็นจะยืนยันว่าคำขอมาจาก Omise จริง และไม่ได้ถูกดัดแปลง ช่วยป้องกันผู้ไม่หวังดีที่พยายามส่ง Webhook ปลอมมายัง Endpoint ของคุณได้อย่างมีประสิทธิภาพ ดูรายละเอียดการใช้งานได้ที่หัวข้อ แนวทางการป้องกัน Endpoint
ทางเลือก: การตรวจสอบ Event (Event Verification)
หากการตรวจสอบลายเซ็นไม่สามารถใช้งานได้ในระบบของคุณ สามารถใช้การตรวจสอบ Event เป็นมาตรการรักษาความปลอดภัยสำรองได้ เมื่อได้รับ Webhook (เช่น charge.complete) ให้ใช้ Resource ID เพื่อสร้างคำขอ GET สำหรับตรวจสอบสถานะ อย่างไรก็ตาม วิธีนี้จะไม่ป้องกัน Webhook ปลอมที่ส่งมายัง Endpoint ของคุณ แต่ช่วยให้สามารถยืนยันความถูกต้องของข้อมูล Event หลังจากได้รับแล้ว
ตัวอย่างการยืนยันความถูกต้องของ Event:
1. Receive `charge.complete` webhook
2. Extract Charge ID from the webhook payload
3. Perform GET request to `/charges/chrg_test_no1t4tnemucod0e51mo`
4. Verify the charge status independently
Event ต่างๆ ที่มีการแจ้งเตือน
Event เกี่ยวกับบัตร
| ชื่อ Event | ทริกเกอร์ |
|---|---|
card.destroy |
ทำลายบัตรแล้ว |
card.update |
อัปเดตบัตรแล้ว |
Event เกี่ยวกับรายการรับชำระเงิน
| ชื่อ Event | ทริกเกอร์ |
|---|---|
charge.capture |
ตัดวงเงินแล้ว (สำหรับการตัดวงเงินด้วยตัวเอง (Manual Capture) เท่านั้น) |
charge.complete |
รับชำระเงินสำเร็จ (หมายเหตุ: รายการรับชำระเงินผ่านบัตรที่ไม่ผ่าน 3DS จะไม่ทริกเกอร์ Event นี้) |
charge.create |
สร้างรายการรับชำระเงินแล้ว |
charge.expire |
รายการรับชำระเงินหมดอายุ (สำหรับ อาลีเพย์ (บาร์โค้ด) เท่านั้น) |
charge.reverse |
ยกเลิกการกันวงเงิน (สำหรับการยกเลิกการกันวงเงินด้วยตนเองเท่านั้น) |
charge.update |
อัปเดตรายการรับชำระเงินแล้ว |
Event เกี่ยวกับลูกค้า
| ชื่อ Event | ทริกเกอร์ |
|---|---|
customer.create |
สร้างลูกค้าแล้ว |
customer.destroy |
ลบลูกค้าแล้ว |
customer.update |
อัปเดตลูกค้าแล้ว |
customer.update.card |
อัปเดตบัตรแล้ว (การอัปเดตผ่านทางลูกค้า) |
Event เกี่ยวกับการปฏิเสธรายการ
| ชื่อ Event | ทริกเกอร์ |
|---|---|
dispute.accept |
รับการปฏิเสธรายการแล้ว |
dispute.close |
ยุติการปฏิเสธรายการแล้ว |
dispute.create |
สร้างการปฏิเสธรายการแล้ว |
dispute.update |
อัปเดตการปฏิเสธรายการแล้ว |
Event เกี่ยวกับลิงก์
| ชื่อ Event | ทริกเกอร์ |
|---|---|
link.create |
สร้างลิงก์แล้ว |
Event เกี่ยวกับบัญชีที่ผูกไว้
| ชื่อ Event | ทริกเกอร์ |
|---|---|
linked_account.create |
สร้างบัญชีที่ผูกไว้แล้ว |
linked_account.complete |
บัญชีที่ผูกไว้ถูกลงทะเบียนสำเร็จโดยลูกค้า |
Event เกี่ยวกับผู้รับโอน
| ชื่อ Event | ทริกเกอร์ |
|---|---|
recipient.activate |
เปิดใช้งานผู้รับโอนแล้ว |
recipient.create |
สร้างผู้รับโอนแล้ว |
recipient.deactivate |
ปิดใช้งานผู้รับโอนแล้ว |
recipient.destroy |
ลบผู้รับโอนแล้ว |
recipient.update |
อัปเดตผู้รับโอนแล้ว |
recipient.verify |
ยืนยันผู้รับโอนแล้ว |
Event เกี่ยวกับการคืนเงิน
| ชื่อ Event | ทริกเกอร์ |
|---|---|
refund.create |
สร้างรายการคืนเงินแล้ว |
Event เกี่ยวกับการตั้งรายการล่วงหน้า
| ชื่อ Event | ทริกเกอร์ |
|---|---|
schedule.create |
สร้างการตั้งรายการล่วงหน้าแล้ว |
schedule.destroy |
ลบการตั้งรายการล่วงหน้าแล้ว |
schedule.expire |
การตั้งรายการล่วงหน้าหมดอายุแล้ว |
schedule.expiring |
การตั้งรายการล่วงหน้าจะหมดอายุเร็วๆ นี้ |
schedule.suspend |
ระงับการตั้งรายการล่วงหน้าแล้ว |
Event เกี่ยวกับรายการโอนเงิน
| ชื่อ Event | ทริกเกอร์ |
|---|---|
transfer.create |
สร้างรายการโอนเงินแล้ว |
transfer.destroy |
ลบรายการโอนเงินแล้ว |
transfer.fail |
รายการโอนเงินถูกทำเครื่องหมายว่าล้มเหลว |
transfer.pay |
รายการโอนเงินถูกทำเครื่องหมายว่าจ่ายแล้ว |
transfer.send |
รายการโอนเงินถูกทำเครื่องหมายว่าส่งแล้ว |
transfer.update |
อัปเดตรายการโอนเงินแล้ว |
Webhook แบบ Dynamic
Webhook แบบ Dynamic เป็นฟีเจอร์ที่ช่วยให้ร้านค้าสามารถกำหนด Endpoint ของแต่ละ Charge ได้เอง โดยระบุพารามิเตอร์ webhook_endpoints ขณะสร้าง Charge
หากไม่ได้ระบุค่า การแจ้งเตือนของ Event ทั้งหมดจะถูกส่งไปยัง Webhook แบบ static ซึ่งเป็นค่าเริ่มต้นสำหรับทุกบัญชีผู้ใช้ของ Omise
ลักษณะการทำงาน
เมื่อมีการระบุ webhook_endpoints:
- การแจ้งเตือน Event ทั้งหมดที่เกี่ยวข้องกับ Charge ที่ระบุจะถูกส่งไปที่
webhook_endpoints - การแจ้งเตือนจะไม่ส่งไปที่ Webhook แบบ static
เมื่อไม่ได้ระบุ webhook_endpoints:
- การแจ้งเตือน Event จะถูกส่งไปยัง static webhook ของบัญชีแทน
แนวทางการป้องกัน Endpoint
Omise ใช้อัลกอริทึม HMAC-SHA256 เพื่อสร้างลายเซ็นดิจิทัลให้กับข้อมูลของ Webhook เมื่อมีการตั้งค่า webhook secret สำหรับบัญชีของคุณ ระบบจะเพิ่มลายเซ็นนี้ไว้ใน Webhook headers ของแต่ละคำขอที่ส่งจาก Omise การตรวจสอบลายเซ็นช่วยให้คุณสามารถยืนยันได้ว่าข้อความ Webhook นั้นถูกส่งมาจาก Omise จริงและข้อมูลภายในไม่ถูกดัดแปลงระหว่างทาง
ลายเซ็นดิจิทัลบน Webhook Headers
| Header | Description |
|---|---|
Omise-Signature |
Omise จะสร้างลายเซ็น HMAC ที่เข้ารหัสแบบ Hex ในระหว่างที่มีการหมุนเปลี่ยน webhook secret เพื่อความปลอดภัย โดย Webhook ที่ส่งออกจาก Omise จะมีลายเซ็นสองค่าใน Header คั่นด้วยเครื่องหมายจุลภาค ลายเซ็นแต่ละค่าจะสอดคล้องกับ Secret ที่แตกต่างกัน |
Omise-Signature-Timestamp |
Unix timestamp ที่ Omise ระบุไว้ใน Webhook header เพื่อบอกเวลาที่สร้างลายเซ็นขึ้นมา |
ตัวอย่าง Header:
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83
Omise-Signature-Timestamp: 1758696391
// During rotation
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83,4bb1e14023f53d076e598245e5e16fd96a94d46072149c483815718eecf2a38d
วิธีตรวจสอบลายเซ็นของ Webhook
ขั้นตอนที่ 1: ดึงค่าลายเซ็น
ดึงค่า Omise-Signature และ Omise-Signature-Timestamp จาก Webhook header
ขั้นตอนที่ 2: เตรียมข้อมูลสำหรับสร้างลายเซ็น
สร้างสตริง Signed Payload เพื่อใช้ตรวจสอบลายเซ็น โดยนำค่าต่อไปนี้มาต่อกัน:
- ค่า
Omise-Signature-Timestamp - เครื่องหมายจุด (
.) - เนื้อหา raw body ของ Webhook (เข้ารหัสแบบ UTF-8)
รูปแบบ:
<TIMESTAMP>.<RAW_BODY>
ขั้นตอนที่ 3: ถอดรหัส Webhook Secret
webhook secret ที่ได้จาก Omise จะถูกเก็บในรูปแบบ Base64 ซึ่งต้องถอดรหัสก่อนนำมาใช้
ขั้นตอนที่ 4: คำนวณลายเซ็นที่ระบบคาดว่าจะได้ (Expected Signature)
คำนวณค่า HMAC-SHA256 digest ของ signed payload จากขั้นตอนก่อนหน้า โดยใช้ค่า webhook secret ที่ถอดรหัสแล้ว จากนั้นแปลงผลลัพธ์ให้อยู่ในรูปแบบ hexadecimal string
ขั้นตอนที่ 5: เปรียบเทียบลายเซ็น
- ตรวจสอบลายเซ็นทั้งหมดใน
Omise-Signatureheader - เปรียบเทียบลายเซ็นแต่ละตัวกับลายเซ็นที่ระบบคาดว่าจะได้ (Expected signature)
- หากไม่มีค่าใดตรงกัน ให้ปฏิเสธคำขอ
- หากมีลายเซ็นใดตรงกัน ถือว่า Webhook นั้นถูกต้องและสามารถประมวลผลต่อได้ทันที นอกจากนี้ คุณสามารถตรวจสอบ Timestamp เพิ่มเติม เพื่อป้องกันการโจมตีแบบ replay attack
ข้อควรระวังด้านความปลอดภัย:
- ป้องกันการโจมตีแบบ Timing Attack: ใช้อัลกอริทึมเปรียบเทียบสตริงแบบใช้เวลาเท่ากัน (constant-time) เมื่อตรวจสอบลายเซ็น
- ป้องกันการโจมตีแบบ Replay Attack (ไม่บังคับ): สำหรับความปลอดภัยเพิ่มเติม สามารถตรวจสอบ timestamp โดยคำนวณความต่างระหว่างเวลาระบบปัจจุบันกับ Timestamp ที่ได้รับ
- หากความต่างเกินกว่าช่วงเวลาที่กำหนด (เช่น 5 นาที) ให้ปฏิเสธ Webhook
- การตรวจสอบ Timestamp เป็นตัวเลือกเสริม แม้ไม่ตรวจสอบ ก็สามารถยืนยันได้ว่า Webhook มาจาก Omise จริง หากลายเซ็นตรงกัน
ตัวอย่างการใช้งาน
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf;
}
}));
function verifySignature({ req }) {
// Step 1: Retrieve the signature(s)
const signatureHeader = req.headers['omise-signature'];
const timestampHeader = req.headers['omise-signature-timestamp'];
// Step 2: Prepare the signed payload
const rawBody = req.rawBody.toString('utf8');
const signedPayload = `${timestampHeader}.${rawBody}`;
// Step 3: Decode the webhook secret
const secret = Buffer.from('<YOUR_WEBHOOK_SECRET>', 'base64');
// Step 4: Compute the expected signature
const expectedBuffer = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest();
// Step 5: Compare against all signatures in the header
const signatures = signatureHeader.split(',');
for (const sig of signatures) {
const sigBuffer = Buffer.from(sig, 'hex');
if (crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
// Optional: Add timestamp validation here
return true;
}
}
return false;
}
app.post('/webhook', (req, res) => {
if (!verifySignature(req)) {
res.status(401).send('Invalid signature')
}
// If verified, return 200 after processing webhook
res.status(200).send('OK');
});
การจัดการ Webhook Secret
ร้านค้าสามารถ ดู สร้าง และจัดการ webhook secret ได้จากส่วนตั้งค่า Webhook ในแดชบอร์ดของ Omise โดย Secret ของแต่ละโหมดการใช้งานจะแยกจากกัน Secret ในโหมดทดสอบไม่สามารถใช้งานในโหมดใช้งานจริงได้
[คำเตือน] Webhook Secret เป็นข้อมูลลับ เช่นเดียวกับรหัสผ่านหรือข้อมูลอ่อนไหวอื่นๆ ห้ามเปิดเผยให้กับโค้ดฝั่งไคลเอนต์ Repositories สาธารณะ หรือแชร์ในสภาพแวดล้อมที่ไม่ปลอดภัยโดยเด็ดขาด
การหมุนเปลี่ยน Webhook Secret เพื่อความปลอดภัย
Omise มีระบบหมุนเปลี่ยน Secret โดยไม่ส่งผลกระทบต่อการให้บริการ สำหรับกรณีที่ร้านค้าจำเป็นต้องอัปเดต Secret เพื่อให้เป็นไปตามข้อกำหนดด้านความปลอดภัย หรือในกรณีที่ Secret รั่วไหล โดยมีรายละเอียดดังนี้
- Rotating a Secret: เมื่อเลือกหมุนเปลี่ยน Secret ระบบจะสร้าง Secret ใหม่ทันที เพื่อป้องกันไม่ให้บริการหยุดทำงาน Secret เดิมจะยังคงใช้งานได้ต่ออีก 24 ชั่วโมง
- Dual-Signatures: ในช่วงเปลี่ยนผ่าน 24 ชั่วโมงนี้ Webhook ที่ส่งออกจาก Omise จะมีลายเซ็นสองค่าใน Header ทั้ง Secret ใหม่และ Secret เดิม โดยแต่ละค่าจะคั่นด้วยเครื่องหมายจุลภาค (,)
- Manual Revocation: ร้านค้าจะไม่สามารถลบ Secret หลักที่กำลังใช้งานอยู่ได้ แต่สามารถเพิกถอน Secret ที่จะหมดอายุได้ในช่วงเปลี่ยนผ่าน 24 ชั่วโมง หากต้องการยุติช่วงเปลี่ยนผ่านทันที
- Capacity: ร้านค้าสามารถมี Secret ที่ใช้งานพร้อมกันได้สูงสุด 2 ค่า ได้แก่ Secret ปัจจุบัน และ Secret ที่กำลังจะหมดอายุ หาก Secret ที่กำลังจะหมดอายุยังคงใช้งานอยู่ ต้องรอให้หมดอายุหรือเพิกถอนด้วยตนเองก่อน จึงจะสามารถ Roll Secret ใหม่ได้อีกครั้ง
ทดสอบการใช้งาน
ก่อนสลับไปใช้งานในโหมดใช้งานจริง เราแนะนำให้ตรวจสอบความถูกต้องของกระบวนการตรวจสอบลายเซ็นในโหมดทดสอบก่อน
- Safe Simulation: การหมุนเปลี่ยนหรือเพิกถอน Secret ในโหมดทดสอบจะไม่ส่งผลกระทบต่อระบบที่ใช้งานจริง
- Verify Rotation Logic: เราขอแนะนำให้ทดสอบขั้นตอนการหมุนเปลี่ยน Secret ในโหมดทดสอบ เพื่อให้มั่นใจว่าโค้ดของคุณสามารถรองรับพฤติกรรม ลายเซ็นคู่ (Dual-Signature) ได้อย่างถูกต้อง ก่อนดำเนินการหมุนเปลี่ยน Secret ในระบบใช้งานจริง
การแก้ไขปัญหา
ปัญหาที่พบบ่อย
ไม่ได้รับ Webhook:
- ตรวจสอบว่า URL ของ Endpoint เป็น HTTPS และมี SSL certificate ที่ถูกต้อง
- ตรวจสอบว่า Firewall ของคุณอนุญาตให้รับคำขอจากเซิร์ฟเวอร์ Omise
- ยืนยันว่า Endpoint ของคุณสามารถเข้าถึงได้ และตอบสนองคำขออย่างถูกต้อง
ตรวจสอบลายเซ็นไม่สำเร็จ:
- ตรวจสอบว่าคุณกำลังใช้ webhook secret ที่ถูกต้อง สำหรับการใช้งานของคุณ (โหมดทดสอบหรือโหมดใช้งานจริง)
- ยืนยันว่าคุณใช้ raw request body โดยไม่ถูกแก้ไขใดๆ
- ตรวจสอบการถอดรหัส Base64 ของ webhook secret ให้ถูกต้อง
ส่ง Events ไปยัง Endpoint ผิด:
- ตรวจสอบว่าได้ระบุพารามิเตอร์
webhook_endpointsระหว่างสร้าง charge หรือไม่ - ตรวจสอบการตั้งค่า static webhook ในแดชบอร์ด