บทความทั้งหมด
webhook debuggingHTTP errorsauthenticationtroubleshooting

ทำไม webhook ของคุณคืน 401 หรือ 403 (และวิธีแก้)

webhook ที่คืน 401 หรือ 403 ตกอยู่ในสาเหตุไม่กี่กลุ่ม ส่วนใหญ่ไม่ใช่แม้แต่ปัญหาลายเซ็น — เป็น middleware หรือค่าเริ่มต้นของเฟรมเวิร์กที่ปฏิเสธคำขอก่อนโค้ดของคุณจะทำงาน นี่คือ checklist วินิจฉัยที่เราใช้ ตามลำดับที่เราใช้

ก่อนอื่น แยก 401 จาก 403

ความหมายต่างกันและวิธีแก้ก็ต่างกัน

401 Unauthorized: เซิร์ฟเวอร์ได้รับคำขอ ดู credential (หรือลายเซ็น) แล้วไม่ยอมรับ handler ของคุณน่าจะทำงาน คำนวณลายเซ็นที่คาดหวัง และปฏิเสธ

403 Forbidden: เซิร์ฟเวอร์ได้รับคำขอและปฏิเสธประมวลผลด้วยเหตุอื่น บ่อยครั้งคำขอไม่เคยถึง handler ของคุณ — middleware หรือค่าเริ่มต้นเฟรมเวิร์กส่ง 403

เปิดการจับคำขอของ tunnel แล้วดู response หากเห็น body response ของ handler ("invalid signature") เป็น 401 จากโค้ดของคุณ หาก response เป็นแบบทั่วไปและ log ของ handler ไม่แสดงอะไร เฟรมเวิร์กปฏิเสธก่อน handler ทำงาน

ห้าสาเหตุที่เป็นไปได้มากที่สุด

1. CSRF middleware (Django, Rails, Laravel)

การป้องกัน CSRF เริ่มต้นปฏิเสธ POST ที่ไม่มี session token ผู้ให้บริการ webhook ไม่ส่งมา อาการ: 403, body response ทั่วไป, ไม่มี log handler

แก้: ยกเว้นเส้นทาง webhook จากการป้องกัน CSRF ใน Django ใช้ @csrf_exempt ใน Rails ใช้ skip_before_action :verify_authenticity_token, only: [:webhook] ใน Laravel เพิ่ม path ไปยัง VerifyCsrfToken::$except คู่มือ webhook Django อธิบายเวอร์ชัน Django ตั้งแต่ต้นจนจบ

2. auth middleware ใช้กว้างเกินไป

คุณเพิ่ม auth middleware แบบ global (JWT, ตรวจ session, ต้องมี API key) และเส้นทาง webhook สืบทอดมา ผู้ให้บริการไม่ส่ง header auth ของคุณ middleware จึงส่ง 401 ก่อน handler ทำงาน

แก้: ยกเว้นเส้นทาง webhook จาก auth middleware การยืนยันตัวตนของ webhook คือลายเซ็น ไม่ใช่สคีมที่ปกป้อง API ที่เหลือ

3. การตรวจสอบลายเซ็นล้มเหลว

handler ทำงาน คำนวณลายเซ็นที่คาดหวัง แล้วไม่ตรง ห้าสาเหตุย่อย เรียงตามความถี่จากมากไปน้อยคร่าว ๆ:

  • body ถูก parse โดย middleware ก่อนการตรวจสอบทำงาน (body ดิบหายไป)
  • การเข้ารหัสผิด (hex vs base64) ดู คู่มือตรวจสอบลายเซ็น
  • secret ผิด (test vs live, dashboard vs CLI, ไฟล์ env vs runtime)
  • timestamp เก่าเกินไป (ลายเซ็นถูกต้องแต่เก่า — น่าจะกำลังทดสอบด้วย payload เก่าที่เล่นซ้ำ)
  • ช่องว่างท้าย secret ที่โหลดจากไฟล์ env

4. ลงทะเบียน URL tunnel ผิด

คุณรีสตาร์ท tunnel และ URL เปลี่ยน แต่ dashboard ผู้ให้บริการยังมีอันเก่า อาการดูเหมือน 401 เพราะคำขอไม่เคยถึงคุณ — แต่จริง ๆ คือคำขออื่นไปถึงเซิร์ฟเวอร์อื่น (มักเป็น session tunnel ก่อนหน้า ซึ่งตอนนี้ปฏิเสธหรือคืน 401)

แก้: ยืนยันว่า URL ใน dashboard ผู้ให้บริการตรงกับ session tunnel ปัจจุบัน หากต้องการ URL คงที่ ดู tunnel แบบมีชื่อหรือ subdomain ที่จอง

5. ข้อจำกัด CORS, content-type หรือ method

พบน้อยกับ webhook แต่เป็นไปได้ หากเส้นทางรับเฉพาะ application/json และผู้ให้บริการส่ง application/x-www-form-urlencoded (เช่น Twilio) บางเฟรมเวิร์กให้ 415 — แต่ที่ตั้งค่าผิดอาจให้ 403 หรือเส้นทางลงทะเบียนเป็น GET แต่ผู้ให้บริการ POST

กระแสวินิจฉัย 90 วินาที

นี่คือลำดับที่เราไล่:

  1. อ่าน body response หากมีคำของ handler คำขอถึง handler ไปดีบักลายเซ็น หากเป็นหน้าข้อผิดพลาดทั่วไปของเฟรมเวิร์ก คำขอไม่ถึง ไปดีบัก middleware
  2. ตรวจ log handler มี log statement ของ handler ยิงไหม ยืนยันว่าคำขอถึงคุณหรือไม่
  3. ตรวจ URL ที่ลงทะเบียน เปิด dashboard ผู้ให้บริการ ยืนยัน URL ตรงกับ tunnel ปัจจุบันและชี้ path ถูก
  4. เทียบ header ขาเข้า การจับ tunnel แสดง header ที่ผู้ให้บริการส่งจริง เทียบกับที่โค้ดอ่าน header ไม่สนตัวพิมพ์แต่วิธีเข้าถึงต่างกันตามเฟรมเวิร์ก — บางอันใช้ request.headers.get('Stripe-Signature') อื่นใช้ request.META['HTTP_STRIPE_SIGNATURE']
  5. ยืนยัน body ดิบตอนเซ็น พิมพ์ความยาว body ก่อนคำนวณลายเซ็น หากเป็นศูนย์หรือเล็กผิดปกติ middleware กินไป
  6. ตรวจ secret ซ้ำ เทียบ env variable ใน runtime กับ dashboard console.log(process.env.WEBHOOK_SECRET.length) — ความยาวตรงกับที่ dashboard แสดงไหม

จากประสบการณ์ ขั้น 1 และ 5 จับ 80% ของเคสภายในสองนาทีแรก

จับคำขอที่ล้มเหลว

เครื่องมือที่มีประโยชน์ที่สุดคือ tunnel ที่มี การจับและเล่นซ้ำคำขอ ไม่ต้องรอให้ผู้ให้บริการลองซ้ำ — คุณเล่นซ้ำคำขอที่จับไว้กับ handler ในเครื่องระหว่างดีบัก ทุกครั้งทันที

หากใช้ tunnel ที่ไม่จับคำขอ คุณดีบักด้วยมือข้างหนึ่งถูกมัดไว้ การเปลี่ยนไปใช้ตัวที่จับได้ (หรือรัน tcpdump หรือวาง nginx หน้าเซิร์ฟเวอร์ dev) คุ้มค่าทันทีที่ประหยัดได้หนึ่งชั่วโมงครั้งแรก

401 เป็นยังไงจากแต่ละผู้ให้บริการ

  • Stripe: 401 จากโค้ดของคุณหมายถึงการตรวจลายเซ็นล้มเหลว dashboard Stripe แสดงการส่งเป็นล้มเหลวพร้อม body response ของคุณ
  • GitHub: หาก handler คืน 401 GitHub ทำเครื่องหมายการส่งเป็นล้มเหลวและลองซ้ำ หน้าการส่งล่าสุดแสดง response
  • Shopify: 401 จาก handler webhook โอเคด้านความปลอดภัยแต่ Shopify จะลองซ้ำ หลังล้มเหลวต่อเนื่อง 19 ครั้งใน 48 ชั่วโมง Shopify ปิดการสมัคร

สำหรับบริบทดีบัก webhook ที่กว้างกว่า ดู วิธีดีบัก webhook ในเครื่อง เข้าร่วมรายชื่อรอของ PortPreview เพื่อ tunnel ที่มีการจับในตัว

คำถามที่พบบ่อย

401 บน webhook หมายความว่าอะไร?
handler ของคุณได้รับคำขอ ประเมิน credential หรือลายเซ็น แล้วปฏิเสธ ที่พบบ่อยที่สุดคือการตรวจลายเซ็นล้มเหลวจากการ parse body ก่อนตรวจสอบ การเข้ารหัสผิด (hex vs base64) หรือ secret ผิด
ทำไมผู้ให้บริการ webhook เห็น 403 จากแอปของฉัน?
403 มักหมายความว่าคำขอไม่เคยถึง handler ของคุณ สาเหตุที่พบบ่อยสุดคือ CSRF middleware (ใน Django, Rails, Laravel) หรือ auth middleware ที่ใช้กว้างเกินไป ยกเว้นเส้นทาง webhook จาก CSRF และ auth ทั่วไปของ API เพื่อให้คำขอถึงโค้ดตรวจสอบของคุณ
จะดีบักความล้มเหลวการยืนยันตัวตนของ webhook โดยไม่ทริกเกอร์อีเวนต์ซ้ำได้อย่างไร?
ใช้ tunnel ที่มีการจับและเล่นซ้ำคำขอ จับการส่งหนึ่งครั้งจากผู้ให้บริการ แล้วเล่นซ้ำกับ handler ในเครื่องกี่ครั้งก็ได้ระหว่างดีบัก ทุกครั้งทันทีและไม่ขึ้นกับผู้ให้บริการ