ตั้ง Webhook LINE บน Vercel แล้ว Event หายไปครึ่งหนึ่ง ต้องแก้ตรงไหนก่อน

สรุปสั้น ๆ
Event ของ LINE Webhook บน Vercel มักหายเพราะ Serverless Function ตอบ Signature Verification ไม่ทัน 200 ภายในเวลาที่ LINE กำหนด หรือเพราะเขียนโค้ดให้รอประมวลผลเสร็จก่อนค่อยตอบกลับ วิธีแก้หลักคือแยก 'รับ Event แล้วตอบ 200 ทันที' ออกจาก 'ประมวลผล Event' และตรวจ Signature ให้ถูกก่อนอ่าน Body เสมอ
ทีมพัฒนาที่เพิ่งต่อ LINE Webhook เข้ากับ Vercel มักเจอปัญหาแบบเดียวกันซ้ำ ๆ คือทดสอบส่งข้อความครั้งแรกได้ผลดี แต่พอใช้งานจริงสักพัก Event บางส่วนกลับไม่มาถึงปลายทางเลย ไม่มี Error ไม่มี Log ที่ชัดเจน มีแต่ความเงียบที่ทำให้ทีมเดาไม่ออกว่าปัญหาอยู่ที่ LINE, ที่ Vercel หรือที่โค้ดของตัวเอง
ปัญหานี้ไม่ได้เกิดจากความไม่เสถียรของ Vercel หรือ LINE แต่เกิดจากลักษณะเฉพาะของ Serverless Function ที่ทำงานต่างจาก Server ปกติที่รันค้างไว้ตลอดเวลา เมื่อไม่เข้าใจข้อจำกัดนี้ โค้ด Webhook ที่ดูเหมือนถูกต้องในตอนทดสอบ ก็จะพังในสถานการณ์จริงที่มี Traffic เข้ามาพร้อมกันหลายทาง
บทความนี้จะไล่ดูตั้งแต่หลักการที่ LINE ใช้ยืนยัน Webhook ไปจนถึงจุดที่ทีมพัฒนาพลาดบ่อยที่สุดเวลาต่อกับ Vercel Function พร้อมตัวอย่างการวางโครงโค้ดที่ทำให้ Event มาครบและตรวจสอบย้อนหลังได้ ระหว่างทางจะพูดถึงการต่อ Database อย่าง line-bot-supabase และการเลือก Runtime แบบ Edge อย่าง line-bot-cloudflare-workers ประกอบเป็นทางเลือกด้วย
LINE ตรวจสอบ Webhook ของคุณด้วยอะไรก่อนเชื่อว่า Endpoint ใช้งานได้จริง
ก่อนที่ LINE จะเริ่มส่ง Event เข้ามาจริง ระบบจะตรวจสอบ Endpoint ที่ตั้งไว้ในหน้า Messaging API Channel ด้วยการส่งคำขอทดสอบไปก่อน ถ้า Endpoint ตอบกลับไม่ใช่ 200 หรือใช้เวลานานเกินไป การยืนยันจะล้มเหลวและ Webhook จะไม่ถูกเปิดใช้งาน จุดนี้เป็นด่านแรกที่ทีมมักเจอปัญหาโดยไม่รู้ตัว เพราะ Vercel Function ตัวแรกที่ยังไม่เคยถูกเรียกใช้งาน (Cold Start) อาจใช้เวลาเริ่มทำงานนานกว่าปกติ
นอกจากการตอบให้ทันเวลา LINE ยังแนบ Header ชื่อ x-line-signature มาด้วยทุกครั้งเพื่อให้ปลายทางตรวจสอบว่า Request นี้มาจาก LINE จริง ไม่ใช่คนภายนอกยิงมาปลอม ๆ วิธีตรวจคือนำ Channel Secret มาคำนวณ HMAC-SHA256 กับ Body ดิบของ Request แล้วเทียบผลลัพธ์กับค่าใน Header ถ้าไม่ตรงกันต้องปฏิเสธ Request ทันที ไม่ใช่ประมวลผลต่อไปเฉย ๆ
จุดที่ทีมพัฒนาพลาดบ่อยคือใช้ Framework ที่ Parse Body เป็น JSON ให้อัตโนมัติก่อนถึงมือโค้ดของเรา ทำให้ Body ดิบที่ต้องใช้คำนวณ Signature หายไปแล้ว การตรวจสอบจึงผิดพลาดทุกครั้งแม้ Request จะมาจาก LINE จริง เวลาตั้งค่า Vercel Function จึงต้องตรวจสอบว่า Body ที่ได้รับเป็น Raw String หรือ Buffer ก่อน ไม่ใช่ Object ที่ถูกแปลงไปแล้ว
หลักการสำคัญที่สุด: ตอบ 200 ให้เร็ว แล้วค่อยประมวลผล Event ทีหลัง
LINE กำหนดเวลาที่ Endpoint ต้องตอบกลับให้ทัน ถ้าปล่อยให้ Function รอประมวลผลเสร็จก่อน เช่น เรียก AI ตอบข้อความ บันทึกลง Database และส่งข้อความกลับ ทั้งหมดในคำขอเดียวกันก่อนค่อยตอบ 200 มีโอกาสสูงที่จะตอบไม่ทัน โดยเฉพาะเมื่อขั้นตอนใดขั้นตอนหนึ่งช้ากว่าปกติ เช่น โมเดล AI ตอบช้าในบางช่วงเวลา
แนวทางที่ทำงานได้เสถียรกว่าคือแยกงานออกเป็นสองส่วนชัดเจน ส่วนแรกคือรับ Event ตรวจ Signature แล้วตอบ 200 กลับทันทีโดยยังไม่ต้องทำอะไรเพิ่ม ส่วนที่สองคือประมวลผล Event จริง เช่น เรียก AI สร้างคำตอบ หรือเขียนข้อมูลลง Database ซึ่งทำได้หลังจากตอบ Client ไปแล้ว
บน Vercel มีสองแนวทางหลักที่ใช้แยกงานแบบนี้ได้ แนวทางแรกคือใช้ Function ที่รองรับการทำงานต่อเบื้องหลังหลังตอบ Response ไปแล้ว (Background Task) ซึ่งต้องเข้าใจข้อจำกัดเรื่องระยะเวลาทำงานสูงสุดของแพ็กเกจที่ใช้อยู่ แนวทางที่สองคือส่ง Event ต่อเข้าคิวหรือ Queue ภายนอก แล้วให้ Worker อีกตัวดึงไปประมวลผลแยกต่างหาก ซึ่งเสถียรกว่าสำหรับงานที่มีปริมาณ Event สูงหรือประมวลผลนาน
วางโครง Event เข้าคิวเมื่อ Traffic เริ่มเยอะขึ้น
ทีมที่เริ่มมี LINE OA ที่ใช้งานจริงและมีข้อความเข้ามาพร้อมกันหลายสิบคนในเวลาใกล้กัน จะเริ่มเห็นปัญหาว่าการประมวลผลแบบ Background Task ในฟังก์ชันเดียวเริ่มไม่พอ เพราะแต่ละ Function มีข้อจำกัดเรื่องจำนวนที่รันพร้อมกันได้และเวลาทำงานสูงสุด การส่ง Event เข้าคิวก่อนแล้วให้ตัวประมวลผลแยกต่างหากดึงไปทำทีละรายการ ช่วยให้ระบบรับมือกับ Traffic ที่พุ่งขึ้นชั่วคราวได้โดยไม่เสีย Event ทิ้ง
รูปแบบที่ใช้กันบ่อยคือ Webhook Endpoint ทำหน้าที่แค่ตรวจ Signature แล้วเขียน Event ลงตาราง Database หรือส่งเข้า Queue Service ทันที จากนั้นตอบ 200 กลับ ส่วน Worker ที่แยกออกไปจะคอยดึง Event ที่ยังไม่ประมวลผลมาทำทีละตัวหรือหลายตัวพร้อมกันตามที่ออกแบบไว้ ถ้า Worker ตัวหนึ่งล้มระหว่างทาง Event ที่ยังไม่เสร็จจะยังอยู่ในคิว ไม่หายไปเฉย ๆ
ข้อดีอีกอย่างของการแยกแบบนี้คือทำให้ Debug ง่ายขึ้นมาก เพราะมีบันทึกทุก Event ที่เข้ามาไว้ในตารางเดียว ตรวจย้อนหลังได้ว่า Event ไหนประมวลผลสำเร็จ ไหนล้มเหลว และล้มเหลวเพราะอะไร ต่างจากการปล่อยให้ทุกอย่างเกิดขึ้นในฟังก์ชันเดียวแล้วหายไปพร้อม Log ที่หาไม่เจอทีหลัง
กันการประมวลผล Event ซ้ำเมื่อ LINE ส่งข้อความเดิมมาอีกครั้ง
อีกพฤติกรรมที่ทีมพัฒนามักไม่รู้ล่วงหน้าคือ LINE อาจส่ง Event เดิมซ้ำมาอีกครั้งได้ในบางสถานการณ์ เช่น Endpoint ตอบช้าจนดูเหมือนล้มเหลว หรือมีปัญหาเครือข่ายชั่วคราว ถ้าโค้ดฝั่งเราไม่มีกลไกกันซ้ำ ผลลัพธ์ที่เกิดขึ้นคือผู้ใช้อาจได้รับข้อความตอบกลับซ้ำสองครั้ง หรือระบบบันทึกข้อมูลผิดเป็นสองรายการ
วิธีป้องกันที่ตรงไปตรงมาคือใช้ Event ID หรือ Message ID ที่แนบมากับแต่ละ Event เป็น Key สำหรับตรวจสอบว่าเคยประมวลผลไปแล้วหรือยัง ก่อนจะเริ่มประมวลผล Event ใด ๆ ให้ตรวจก่อนว่า ID นี้เคยถูกบันทึกไว้แล้วหรือไม่ ถ้าเคยแล้วให้ข้ามไปเลยโดยไม่ต้องทำซ้ำ วิธีนี้ทำให้ระบบทนต่อการได้รับ Event ซ้ำโดยไม่ต้องพึ่งว่า LINE จะไม่ส่งซ้ำเลย
ในทางปฏิบัติมักสร้างคอลัมน์ Unique Constraint บน Event ID ไว้ในตาราง Database เพื่อให้การเขียนข้อมูลซ้ำล้มเหลวโดยอัตโนมัติในระดับฐานข้อมูล ไม่ต้องพึ่งการเช็คเงื่อนไขในโค้ดฝั่งแอปพลิเคชันเพียงอย่างเดียว ซึ่งเสี่ยงพลาดเมื่อมีหลาย Instance ของ Function ทำงานพร้อมกัน
เลือก Runtime บน Vercel ให้เหมาะกับลักษณะงานของ Webhook
Vercel เปิดให้เลือก Runtime ได้มากกว่าหนึ่งแบบสำหรับ Function หนึ่งตัว ซึ่งมีผลต่อความเร็วในการเริ่มทำงานและข้อจำกัดที่ใช้ได้ Runtime แบบ Edge มักเริ่มทำงานเร็วกว่าเพราะรันใกล้ผู้ใช้และไม่มี Cold Start แบบ Node.js Function ทั่วไป แต่ก็มีข้อจำกัดเรื่อง Library บางตัวที่ใช้ Node API เฉพาะไม่ได้
สำหรับ Webhook ที่ทำหน้าที่แค่รับ Event ตรวจ Signature แล้วส่งต่อเข้าคิวหรือ Database Edge Runtime มักเพียงพอและช่วยลดปัญหาการตอบไม่ทันเวลาที่ LINE กำหนดได้ดี ส่วนงานที่ต้องประมวลผลหนักกว่า เช่น เรียก AI สร้างคำตอบ หรือประมวลผลไฟล์ มักเหมาะกับ Node.js Function มากกว่าเพราะรองรับ Library ได้กว้างกว่า
การแยกงานตามที่อธิบายไว้ข้างต้นยังช่วยให้เลือก Runtime ได้อิสระมากขึ้นด้วย เพราะ Function ที่รับ Webhook กับ Function หรือ Worker ที่ประมวลผลจริงไม่จำเป็นต้องใช้ Runtime เดียวกัน ทีมสามารถเลือก Edge สำหรับส่วนรับ Event ที่ต้องเร็ว และเลือก Node.js สำหรับส่วนประมวลผลที่ต้องใช้ Library เฉพาะทาง
จัดการ Channel Secret และ Access Token ให้ปลอดภัยบน Vercel
Channel Secret ที่ใช้ตรวจ Signature และ Channel Access Token ที่ใช้ส่งข้อความกลับ เป็นข้อมูลที่ต้องเก็บเป็น Environment Variable บน Vercel เท่านั้น ห้าม Hardcode ไว้ในโค้ดหรือ Commit ขึ้น Repository เด็ดขาด เพราะถ้าหลุดออกไป ผู้ไม่หวังดีสามารถส่งข้อความปลอมในนาม LINE OA ของธุรกิจได้ทันที
Vercel แยก Environment Variable ได้ตาม Environment เช่น Production, Preview และ Development ทำให้ทีมสามารถใช้ LINE Channel คนละตัวสำหรับทดสอบกับใช้งานจริงได้ ลดความเสี่ยงที่การทดสอบจะไปกระทบผู้ใช้จริงโดยไม่ตั้งใจ ควรตั้งค่าแยกกันตั้งแต่ต้น แทนที่จะใช้ Channel เดียวกันทั้งทดสอบและใช้งานจริง
อีกจุดที่ควรทำคือหมุนเวียน (Rotate) Access Token เป็นระยะตามนโยบายความปลอดภัยของทีม และตรวจสอบ Log การเข้าถึง Environment Variable ว่ามีเฉพาะคนที่จำเป็นต้องเห็นเท่านั้น โดยเฉพาะในทีมที่มีสมาชิกหลายคนเข้าถึง Vercel Project เดียวกัน
เก็บ Event ลง Database เพื่อวิเคราะห์และ Debug ย้อนหลัง
การเก็บ Event ทุกตัวที่ได้รับไว้ในตาราง Database ไม่ใช่แค่ช่วยกันประมวลผลซ้ำ แต่ยังเป็นแหล่งข้อมูลสำคัญเวลาต้องสืบว่าเกิดอะไรขึ้นกับผู้ใช้คนใดคนหนึ่ง เช่น ผู้ใช้แจ้งว่าส่งข้อความไปแล้วไม่ได้รับคำตอบ ทีมสามารถค้นจาก User ID และช่วงเวลาที่แจ้งมา เพื่อดูว่า Event นั้นเข้ามาจริงหรือไม่ ประมวลผลสำเร็จหรือล้มเหลวตรงไหน
การเลือกใช้ Postgres อย่าง Supabase ต่อกับ Vercel เป็นแนวทางที่พบบ่อย เพราะตั้งค่าได้เร็วและมี Client Library ที่เชื่อมกับ Serverless Function ได้ตรงไปตรงมา รายละเอียดเรื่องการออกแบบตารางและการเชื่อมต่อจาก Edge Function อธิบายไว้ต่อใน line-bot-supabase ซึ่งใช้หลักการเดียวกับที่พูดถึงในบทความนี้
โครงตารางขั้นต่ำที่ควรมีคือ Event ID, Event Type, User ID, เวลาที่ได้รับ, สถานะการประมวลผล และ Payload ดิบสำหรับตรวจย้อนหลังได้ครบ การเก็บ Payload ดิบไว้ด้วยแม้จะใช้พื้นที่มากขึ้น แต่ช่วยได้มากเวลาต้องแก้ปัญหาที่ไม่คาดคิดล่วงหน้า เพราะข้อมูลที่แยกออกมาไว้ก่อนอาจไม่ครอบคลุมทุกกรณี
ตั้ง Monitoring ให้รู้ทันทีเมื่อ Webhook เริ่มมีปัญหา
ปัญหาของ Webhook ที่แย่ที่สุดคือมันมักไม่ล้มเหลวแบบทันทีจนสังเกตเห็นง่าย ๆ แต่ล้มเหลวแบบเงียบ ๆ เป็นบางส่วน เช่น Event มาไม่ครบ 5-10 เปอร์เซ็นต์ในบางช่วงเวลา ซึ่งง่ายที่จะไม่มีใครสังเกตจนผู้ใช้เริ่มบ่นว่าแชทไม่ตอบ
การตั้ง Monitoring พื้นฐานที่ควรมีตั้งแต่ต้นคือนับจำนวน Event ที่ Endpoint ได้รับต่อชั่วโมง เทียบกับจำนวนที่ประมวลผลสำเร็จ ถ้าตัวเลขทั้งสองเริ่มห่างกันผิดปกติ ควรมีการแจ้งเตือนอัตโนมัติ เช่นส่งเข้า Slack หรือ Email ของทีม Dev แทนที่จะรอให้ผู้ใช้แจ้งปัญหาเข้ามาก่อน
นอกจากนี้ควรเก็บ Log เวลาตอบกลับ (Response Time) ของ Endpoint ไว้ด้วย เพราะถ้าเวลาตอบเริ่มขยับสูงขึ้นเรื่อย ๆ แม้ยังไม่ถึงจุดที่ Timeout เป็นสัญญาณเตือนล่วงหน้าว่าระบบกำลังเข้าใกล้ขีดจำกัด ควรแก้ก่อนที่จะกลายเป็นปัญหา Event หายจริง ไม่ใช่รอให้เกิดปัญหาก่อนค่อยไปหาสาเหตุ
สรุปสาเหตุ Event หายที่พบบ่อย เทียบกับวิธีแก้
ตารางนี้สรุปอาการที่ทีมพัฒนามักเจอเวลาต่อ LINE Webhook กับ Vercel พร้อมสาเหตุที่เป็นไปได้และแนวทางแก้ที่ตรงจุด:
| อาการ | สาเหตุที่เป็นไปได้ | แนวทางแก้ |
|---|---|---|
| Webhook ยืนยันไม่ผ่านตอนตั้งค่าครั้งแรก | Cold Start ทำให้ตอบช้าเกินเวลาที่กำหนด | แยก Endpoint ให้ทำงานเบา ตอบ 200 ก่อนประมวลผล |
| Signature ไม่ตรงทุกครั้งทั้งที่ Secret ถูก | Framework Parse Body เป็น JSON ก่อนคำนวณ Signature | ตั้งค่าให้รับ Raw Body ก่อนคำนวณ HMAC |
| Event บางส่วนหายไปเฉย ๆ ไม่มี Log | ประมวลผลในฟังก์ชันเดียวจนตอบไม่ทันเวลา | แยกรับ Event กับประมวลผลออกจากกัน ใช้คิว |
| ผู้ใช้ได้รับข้อความตอบซ้ำสองครั้ง | ไม่มีกลไกกันประมวลผล Event ซ้ำ | ใช้ Event ID เป็น Unique Key กันซ้ำระดับ Database |
สรุป
ปัญหา Event หายบน LINE Webhook ที่รันบน Vercel ส่วนใหญ่ไม่ได้เกิดจากความไม่เสถียรของแพลตฟอร์มใดแพลตฟอร์มหนึ่ง แต่เกิดจากการออกแบบโค้ดที่รอประมวลผลเสร็จก่อนค่อยตอบ Response ทำให้ตอบไม่ทันเวลาที่ LINE กำหนดในบางสถานการณ์ที่มี Traffic เข้ามาพร้อมกัน
การแยกงานเป็นสองชั้นคือรับ Event ตอบ 200 ทันที แล้วค่อยประมวลผลทีหลังผ่านคิวหรือ Background Task พร้อมกับมีกลไกกัน Event ซ้ำและ Monitoring ที่แจ้งเตือนล่วงหน้า คือโครงพื้นฐานที่ทำให้ระบบรับมือกับ Traffic จริงได้โดยไม่ต้องมานั่งไล่หา Event ที่หายไปทีหลัง
- ตรวจ Signature จาก Raw Body เสมอ อย่าปล่อยให้ Framework แปลงเป็น JSON ก่อน
- แยกรับ Event กับประมวลผลออกจากกัน ตอบ 200 ให้เร็วที่สุดเสมอ
- ใช้ Event ID เป็น Unique Key กันประมวลผลซ้ำระดับ Database ไม่ใช่แค่เช็คในโค้ด
คำถามที่พบบ่อย
ต้องใช้ Vercel Pro ไหมถึงจะรัน LINE Webhook ได้เสถียร
ไม่จำเป็นเสมอไป แพ็กเกจฟรีใช้งานได้สำหรับ Traffic น้อย แต่ถ้ามี Event เข้ามาจำนวนมากพร้อมกันหรือต้องการเวลาทำงานของ Function ที่นานขึ้น ควรตรวจข้อจำกัดของแต่ละแพ็กเกจจากหน้า Pricing ปัจจุบันก่อนตัดสินใจอัปเกรด
ทำไม Webhook ทดสอบผ่านตอนสร้างครั้งแรก แต่พังตอนใช้งานจริง
ส่วนใหญ่เกิดจากตอนทดสอบมี Event เข้ามาทีละตัว Function จึงตอบทันเวลาเสมอ แต่พอใช้งานจริงมี Event เข้ามาพร้อมกันหลายทาง ทำให้บาง Instance ตอบช้าเกินเวลาที่ LINE กำหนด ควรทดสอบด้วยการยิง Event พร้อมกันหลายรายการก่อนใช้งานจริง
ควรเก็บ Payload ดิบของทุก Event ไว้จริงหรือ เปลืองพื้นที่ไหม
แนะนำให้เก็บอย่างน้อยในช่วงแรกของการใช้งาน เพราะช่วย Debug ปัญหาที่ไม่คาดคิดได้มาก เมื่อระบบเสถียรแล้วค่อยพิจารณาตั้ง Retention Policy ลบข้อมูลเก่าที่ไม่จำเป็นออกเป็นระยะเพื่อลดพื้นที่ใช้งาน
ใช้ Edge Function กับ Node.js Function ต่างกันมากแค่ไหนสำหรับงานนี้
Edge Function เริ่มทำงานเร็วกว่าและเหมาะกับงานเบาอย่างการรับ Event แล้วส่งต่อ ส่วน Node.js Function รองรับ Library ได้กว้างกว่าเหมาะกับงานประมวลผลที่ซับซ้อน ทีมสามารถแยกใช้สอง Runtime สำหรับสองส่วนของระบบได้พร้อมกัน
ถ้าไม่ใช้ Queue Service แยก ต้องเปลี่ยนสถาปัตยกรรมทั้งหมดเลยไหม
ไม่จำเป็นต้องเปลี่ยนทั้งหมดในคราวเดียว เริ่มจากบันทึก Event ลงตาราง Database พร้อมสถานะประมวลผลก่อนก็เพียงพอสำหรับ Traffic ระดับกลาง แล้วค่อยเพิ่ม Queue Service เมื่อปริมาณ Event เริ่มสูงจนตารางเดียวไม่พอรองรับ
จำเป็นต้องมี Monitoring ตั้งแต่วันแรกที่เปิดใช้งานจริงไหม
ควรมีอย่างน้อยการนับจำนวน Event ที่ได้รับต่อชั่วโมงตั้งแต่วันแรก เพราะปัญหา Event หายมักเกิดแบบเงียบ ๆ การมีตัวเลขพื้นฐานให้เทียบตั้งแต่ต้นช่วยให้เห็นความผิดปกติได้เร็วกว่าการรอให้ผู้ใช้แจ้งปัญหาเข้ามาก่อน
ลองตรวจด้วยตัวเอง
LINE Webhook Payload Inspector
วาง JSON payload จาก LINE แล้วดูว่าเป็น event ชนิดไหน มี field ครบตาม schema ปัจจุบันหรือไม่
ตรวจ payload ฟรี →ใช้ฟรี ไม่ต้องสมัคร ไม่เก็บข้อมูลที่คุณกรอก
อ่านต่อแบบเจาะลึก
ทดลองวัดผลโฆษณาเข้า LINE ฟรี 14 วัน
สมัครแล้วสร้างโปรเจกต์ เชื่อม LINE ติดตั้ง Tracking แล้วเริ่มเห็น Funnel จาก Click ถึง Purchase ได้ทันที ไม่ต้องใช้บัตรเครดิต
เริ่มทดลองใช้ฟรี 14 วันบทความที่เกี่ยวข้อง

ผู้ใช้ทักเข้ามาวันละหลักพันข้อความ แต่ Bot เริ่มค้างและตอบช้าเป็นบางช่วง เกิดจากอะไร

เปิดใช้งาน Bot ได้ไม่ถึงเดือน ผู้ใช้เริ่มบ่นว่าทักไปแล้วเงียบ ต้องแก้ยังไง
