บั๊ก Shopify webhook ส่วนใหญ่ในการพัฒนาในเครื่องสรุปได้ที่บรรทัดเดียว: HMAC เข้ารหัสแบบ base64 ไม่ใช่ hex ถ้าคุณเรียก .digest('hex') แล้วเทียบกับ X-Shopify-Hmac-Sha256 การตรวจจะไม่มีวันผ่าน — แม้มี shared secret ที่ถูกต้อง เราเห็นวิศวกรซีเนียร์เสียทั้งบ่ายไปกับอักขระตัวเดียวนี้
กับดัก base64
Stripe ใช้ hex GitHub ใช้ hex (มี prefix sha256=) Shopify ใช้ base64 สามผู้ให้บริการ สามการเข้ารหัส เวอร์ชันแรกของตัวตรวจของคุณแทบแน่นอนจะคัดลอก snippet จากโปรเจกต์อื่นและล้มเหลวเงียบ ๆ
การตรวจควรเป็นแบบนี้:
const hmac = crypto
.createHmac('sha256', SHOPIFY_API_SECRET)
.update(rawBody) // raw body, not JSON-parsed
.digest('base64'); // base64, not hex
const valid = crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(req.headers['x-shopify-hmac-sha256']),
);
นอกจากการเข้ารหัส มีสองสิ่งสำคัญ body ต้องเป็นไบต์ดิบที่ยังไม่พาร์ส และการเทียบต้องเป็น timing-safe — การเทียบ string เท่ากันรั่วข้อมูลทีละไบต์
ทำไม tunnel ทำให้ง่ายขึ้น
คุณยังใช้คำสั่ง Shopify shopify app dev ได้ ซึ่งตั้ง tunnel ภายในให้ มันทำงานได้ แต่มันห่อเซิร์ฟเวอร์ dev ในกระบวนการของมันเอง กลืน log แบบเฉพาะ และไม่ให้ปุ่ม replay สำหรับการพัฒนาแอปเกิน "hello world" URL สาธารณะที่เสถียรพร้อม tunnel localhost ที่จับคำขอ ประหยัดเวลามากกว่าที่ CLI ประหยัดในการตั้งค่าเริ่มต้น
npx portpreview 3000
คุณวาง URL HTTPS ในการตั้งค่า webhook ของแอปในแดชบอร์ด Partners ทริกเกอร์อีเวนต์จากร้านทดสอบ และคำขอลงใน handler ในเครื่องพร้อม header ครบ
ร้านทดสอบ: ส่วนที่เอกสาร Shopify ผ่านเร็ว
สองสิ่งที่ควรรู้ก่อนต่ออะไร:
- ร้าน development ยิงทุกอีเวนต์ webhook การสร้างคำสั่งซื้อ fulfillment คลังสินค้า — ทั้งหมด ไม่ต้องปลอมอะไร แค่ติดตั้งแอปบนร้าน dev แล้วคลิก
- secret ของ webhook ต่างกันต่อแอปและต่อช่องทางส่ง ถ้าคุณ subscribe EventBridge หรือ Pub/Sub ด้วย พฤติกรรม HMAC ต่างกัน ที่นี่เราพูดถึงการส่ง webhook HTTPS แบบธรรมดา
การตั้งค่าทีละขั้น
- เริ่มแอปในเครื่องบนพอร์ตที่ใช้ (3000 เป็นที่นิยม)
- รัน
npx portpreview 3000และคัดลอก URL HTTPS ที่พิมพ์ออกมา - ในแดชบอร์ด Shopify Partners เปิดแอปและไปที่ Configuration → Webhooks
- ตั้ง endpoint webhook เป็น
https://your-tunnel.portpreview.dev/api/webhooks/shopify(หรือ path ที่แอปใช้) - ติดตั้งแอปบนร้าน dev แล้วทริกเกอร์อีเวนต์ — สร้างคำสั่งซื้อร่าง fulfill รายการ เปลี่ยนคลัง
- ดูคำขอลงมา ตรวจ header และ body replay คำขอที่จับไว้หลังแก้ handler ทุกครั้ง
ความผิดพลาดที่เราเห็นซ้ำ
body ถูกพาร์สก่อนตรวจ
ถ้าคุณวาง app.use(express.json()) ก่อน route webhook ไบต์ดิบหายไปแล้วตอนคุณพยายามตรวจ mount raw-body parser เฉพาะ path webhook หรือดึง body เองจาก request stream ก่อนการพาร์ส JSON ใด ๆ
สับสน secret
secret ของแอป Partners ไม่ใช่ token Storefront API HMAC ของ webhook ใช้ secret ของแอป ถ้าคุณจ้อง shp_xxx ในไฟล์ env คุณหยิบผิดตัว
อีเวนต์ทดสอบที่ดูเหมือนกัน
ปุ่ม "Send test notification" ของ Shopify ส่งอีเวนต์สังเคราะห์ที่มี body ตัวยึด ลายเซ็นบนอีเวนต์ทดสอบนั้นเป็นของจริง แต่ payload คงที่ สำหรับทดสอบรูปร่าง payload จริง ให้ทริกเกอร์อีเวนต์จากร้าน dev เอง
replay ต่อรองไม่ได้สำหรับ dev แอป
Shopify ลอง webhook ที่ล้มเหลวซ้ำได้ถึง 48 ชั่วโมงด้วย exponential backoff ใจกว้าง แต่ตอน iterate คุณไม่อยากรอ retry ครั้งถัดไป จับการส่งครั้งแรกใน ประวัติคำขอของ tunnel และ replay ตามต้องการกับ handler ในเครื่อง
เมื่อในเครื่องไม่พออีกต่อไป
flow การลงทะเบียนร้าน webhook GDPR และการเปลี่ยนการเรียกเก็บเงิน subscription ตรวจง่ายกว่าบนสภาพแวดล้อมที่ deploy เพราะมันโต้ตอบกับสถานะบัญชีของ Shopify เอง สำหรับที่เหลือ — การพาร์ส payload การตรวจลายเซ็น ตรรกะธุรกิจ — ในเครื่องเร็วกว่า
สำหรับคณิตศาสตร์ลายเซ็นพื้นฐานข้ามทุกผู้ให้บริการ อ่าน คู่มือการตรวจลายเซ็น webhook ของเรา เข้าร่วมรายชื่อรอ PortPreview ถ้าคุณอยากได้ tunnel ที่มี replay ในตัว