วันนึงผมเปิด Facebook Ads Manager ดูแคมเปญ Newton เห็นมันรายงานว่าได้ลูกค้า (Purchase) 8 คน — แต่พอผมเปิด Stripe ดูยอดจริง มันเก็บเงินไปแล้ว 15 คนครับ หายไปเกือบครึ่ง 555 ตอนแรกผมนึกว่า Facebook นับช้า แต่รอไป 2 วันตัวเลขก็ยังไม่ขยับ ผมเลยให้ทิม (AI Agent ของผม) ไปไล่ดูว่าเกิดอะไรขึ้น — เจอว่าปัญหาไม่ได้อยู่ที่ Facebook นับช้า แต่ระบบ tracking ของผมเองโดน "บล็อก" ทิ้งไปดื้อๆ ตั้งแต่ฝั่งเบราว์เซอร์ลูกค้า
ทำไม Facebook ถึงนับขาด
เวลาเราติด Facebook Ads เราจะฝัง code เล็กๆ ที่ชื่อ Pixel ไว้บนหน้าเว็บครับ พอลูกค้าเข้าหน้า ซื้อของ สมัคร อะไรพวกนี้ Pixel มันจะยิง event กลับไปบอก Facebook ว่า "คนนี้ทำ action นี้นะ มาจากแอดตัวไหน" — Facebook ก็เอาไปคำนวณว่าแอดตัวไหนคุ้ม ตัวไหนควรปิด
ปัญหาคือ Pixel มันทำงานฝั่ง เบราว์เซอร์ลูกค้า (client-side) ล้วนๆ ซึ่งสมัยนี้โดนบล็อกง่ายมาก:
- Ad blocker — คนติดส่วนขยายบล็อกโฆษณากันเยอะ พวกนี้บล็อก Pixel เป็นอย่างแรกเลย
- Safari / iOS (ITP) — Apple มี Intelligent Tracking Prevention ตัด third-party tracking ทิ้งเกือบหมด คนไทยใช้ iPhone เยอะมาก ยอดเลยหายเป็นกอบเป็นกำ
- เน็ตหลุดกลางทาง — ลูกค้าจ่ายเงินเสร็จ หน้าเด้งไป success แต่เน็ตสะดุดแป๊บนึง Pixel ยังไม่ทันยิง event ก็หายไปเลย
พูดง่ายๆ คือ event ที่สำคัญที่สุด — Purchase — ดันเป็นตัวที่ "เดินทางไกลที่สุด" กว่าจะถึง Facebook (ผ่านเบราว์เซอร์ลูกค้า ผ่าน ad blocker ผ่าน ITP) เลยตกหล่นมากที่สุด ส่วน Stripe ที่เก็บเงินจริงฝั่ง server ไม่มีทางพลาด — ตัวเลขสองฝั่งเลยห่างกันลิบ
ทางแก้: ยิง event ซ้ำจากฝั่ง server ด้วย (CAPI)
Facebook มีของที่เรียกว่า Conversions API (CAPI) — แทนที่จะยิง event จากเบราว์เซอร์ลูกค้าอย่างเดียว เราก็ยิงจาก server ของเราตรงเข้า Facebook อีกทางหนึ่ง ฝั่ง server มันโดน ad blocker หรือ ITP บล็อกไม่ได้ครับ เพราะมันคุยกันเครื่องต่อเครื่องเลย ไม่ผ่านเบราว์เซอร์ลูกค้า
ทิมเลยเขียน helper ตัวนึงชื่อ facebook_capi() ไว้ใน backend ของ Newton ทุกครั้งที่มีคนสมัครเสร็จ — เหมือนตอนที่ผมให้ทิมไล่ปิดช่องโหว่ Stripe webhook ก่อนหน้านี้ — ตัว webhook ของ Stripe ที่ยิงเข้ามาบอกว่า "จ่ายเงินสำเร็จ" จะ trigger ให้ server ยิง Purchase event เข้า Facebook ทันที โดยไม่ต้องรอให้เบราว์เซอร์ลูกค้ายิงเอง
ฟังดูง่ายใช่ไหมครับ — แต่มันมีกับดักอยู่ตรงนี้แหละ
กับดัก: ยิงสองทาง = นับซ้ำ 2 เท่า
พอเรายิง Purchase ทั้งจากเบราว์เซอร์ (Pixel) และจาก server (CAPI) สำหรับลูกค้าคนเดียวกัน — ถ้าทั้งสอง event ส่งถึง Facebook ทั้งคู่ มันจะกลายเป็นว่า "ลูกค้า 1 คน = Purchase 2 ครั้ง"
แทนที่จะแก้ปัญหานับขาด เรากลับไปสร้างปัญหานับเกินแทน ตัวเลขก็ยังผิดอยู่ดี — แค่ผิดไปอีกทาง
วิธีที่ Facebook ออกแบบมาให้แก้คือ deduplication ด้วย event_id ครับ ไอเดียคือ ทั้ง Pixel และ CAPI ต้องยิง event ที่ "ตัวเดียวกัน" ด้วย ID เดียวกัน พอ Facebook เห็น event_id ซ้ำ มันจะรู้ว่า "อ๋อ นี่ event เดียวกัน มาจาก 2 ทาง" แล้วเก็บไว้แค่อันเดียว
กฎคือ event_id ต้องมาจาก "ของจริงที่ไม่ซ้ำกัน" ของลูกค้าคนนั้น ทิมเลยใช้ stripe_sub_id (เลข subscription ที่ Stripe สร้างให้ตอนสมัคร) เป็น event_id ของ Purchase — เพราะมันไม่มีทางซ้ำ และทั้งฝั่งเบราว์เซอร์กับฝั่ง server ต่างก็รู้จักเลขนี้:
# ฝั่งเบราว์เซอร์ (Pixel) — ยิงตอนหน้า success โหลด
fbq('track', 'Purchase', {value: 990, currency: 'THB'},
{eventID: 'sub_1Q8xZ...'}); // ← event_id = stripe_sub_id
# ฝั่ง server (CAPI) — ยิงตอน Stripe webhook เข้ามา
facebook_capi(
event_name = "Purchase",
event_id = "sub_1Q8xZ...", # ← ตัวเดียวกันเป๊ะ
value = 990, currency = "THB",
)
# Facebook เห็น event_id ซ้ำ → merge เหลือ Purchase 1 ครั้ง
หลักการง่ายๆ คือ: ยิงให้เกินไว้ก่อน ดีกว่ายิงขาด — เพราะการนับเกินแก้ได้ด้วย dedup แต่การนับขาดมันหายไปแล้วหายเลย เอากลับมาไม่ได้ ทิมเลยไล่ทำ pattern เดียวกันนี้กับทุก event ที่สำคัญ ไม่ใช่แค่ Purchase:
- Purchase — dedup ด้วย
stripe_sub_id - InitiateCheckout (กดเริ่มจ่ายเงิน) — dedup ด้วย
session_id - newton_pageview (เข้าหน้าขาย) — dedup ด้วย
event_idที่ frontend สร้างแล้วส่งไปทั้งสองทาง
ผลลัพธ์: ตัวเลขสองฝั่งมาเจอกัน
หลัง ship ไปไม่กี่วัน ตัวเลข Purchase ใน Facebook Ads Manager กับยอดจริงใน Stripe เริ่มวิ่งเข้าหากันครับ จากที่เคยห่างกันเกือบครึ่ง เหลือต่างกันแค่นิดเดียว (ซึ่งปกติ เพราะมันมีดีเลย์เล็กๆ ระหว่างสองระบบอยู่แล้ว)
เรื่องนี้ไม่ใช่แค่ "ตัวเลขสวยขึ้น" นะครับ — มันสำคัญตรงที่ Facebook เอายอด conversion ไป optimize แอดให้เรา ถ้าเราป้อนข้อมูลให้มันขาดๆ หายๆ มันก็จะ optimize ผิด ไปหาคนที่ไม่ซื้อ เผาเงินเปล่า พอ tracking แม่นขึ้น ระบบ optimize ของ Facebook ก็ทำงานได้ตรงเป้าขึ้น — เงินค่าแอดทุกบาทเลยทำงานคุ้มขึ้น แบบเดียวกับตอนที่ผมให้ทิมดูแลแคมเปญ Facebook ทั้งหมด
บทเรียนจากงานนี้
1. ตัวเลขใน dashboard ไม่ใช่ความจริงเสมอไป — ผมเกือบจะปิดแคมเปญที่จริงๆ แล้วทำกำไรได้ เพราะเชื่อตัวเลขที่ Facebook รายงานมาดื้อๆ บทเรียนคือ เอายอดในแพลตฟอร์มโฆษณามาเทียบกับ "ยอดเงินจริง" (Stripe / bank) เสมอ ถ้ามันห่างกันเกิน 10-20% แปลว่า tracking มีรูรั่ว
2. อะไรที่วิ่งผ่านเบราว์เซอร์ลูกค้า = เชื่อ 100% ไม่ได้ — ad blocker, ITP, เน็ตหลุด, แท็บปิดก่อนโหลดเสร็จ มีพันแบบที่ทำให้ client-side event หาย ของสำคัญต้องมี server-side สำรองไว้เสมอ
3. dedup คือหัวใจของ tracking สองทาง — ถ้าจะยิงทั้ง client + server ต้องวาง event_id ให้ตรงกันตั้งแต่แรก ไม่งั้นแก้นับขาดเสร็จก็ไปเจอนับเกินแทน กุญแจคือเลือก "ของจริงที่ไม่ซ้ำ" ของ transaction นั้นมาเป็น ID — อย่ามั่วสุ่มเลขเอาเอง
4. งานพวกนี้ "ไม่เร่งด่วน" แต่กินเงินเงียบๆ ทุกวัน — tracking รั่วไม่ทำให้เว็บล่ม ไม่มีลูกค้าโทรมาด่า มันแค่ทำให้เราตัดสินใจผิดเรื่องแอดเงียบๆ ทุกวัน งานแบบนี้แหละที่ผมชอบโยนให้ทิมทำ เพราะมันเป็นงานที่คนมักผัดวันไปเรื่อยๆ จนเสียเงินไปเยอะแล้วถึงรู้ตัว
ผมไม่ได้แตะ code สักบรรทัด
ทั้งงานนี้ผมแค่บอกทิมว่า "Facebook รายงาน Purchase น้อยกว่า Stripe เกือบครึ่ง ไปดูทีว่าเกิดอะไรขึ้น" แล้วทิมก็ไปไล่เอง — เทียบยอดสองฝั่ง, รู้ว่าเป็นปัญหา client-side tracking โดนบล็อก, รู้จัก CAPI, วาง dedup ด้วย event_id, ไล่ทำให้ครบทุก event, แล้วเทสต์จนตัวเลขมาเจอกัน ผมแค่อยู่หลังจอ approve กับดูผลลัพธ์
นี่คือสิ่งที่ผมชอบที่สุดของการมี AI Agent ส่วนตัวครับ — งานเทคนิคลึกๆ ที่ปกติต้องจ้าง dev มาทำ หรือไม่ก็ปล่อยรั่วไปเรื่อยๆ เพราะไม่มีเวลา มันจัดการให้จบได้ในวันเดียว
คำถามที่พบบ่อย
ทำไม Facebook Ads รายงาน conversion น้อยกว่ายอดจริงใน Stripe
เพราะ Facebook Pixel ทำงานฝั่งเบราว์เซอร์ลูกค้าครับ ซึ่งโดนบล็อกได้ง่ายจาก ad blocker, Safari ITP, หรือเน็ตหลุดกลางทางก่อน event ส่งถึง Facebook ส่วน Stripe เก็บเงินฝั่ง server ไม่ผ่านเบราว์เซอร์เลย เลยไม่มีโอกาสพลาด ตัวเลขสองฝั่งจึงห่างกัน
Facebook Conversions API (CAPI) คืออะไร และต่างจาก Pixel ยังไง
CAPI คือการยิง event จาก server ของเราตรงเข้า Facebook โดยไม่ผ่านเบราว์เซอร์ลูกค้าครับ Pixel ยิงจากฝั่ง client อาจโดนบล็อก ส่วน CAPI ยิงจากฝั่ง server คุยกันเครื่องต่อเครื่อง โดนบล็อกไม่ได้ วิธีที่ดีที่สุดคือใช้ทั้งสองทางพร้อมกัน แล้วใช้ event_id ป้องกันนับซ้ำ
event_id deduplication ใน Facebook tracking ทำงานยังไง
ทั้ง Pixel และ CAPI ต้องส่ง event_id ตัวเดียวกันสำหรับ transaction เดียวกันครับ พอ Facebook เห็น event_id ซ้ำจากสองแหล่ง มันจะรู้ว่าเป็น event เดียวกัน แล้วเก็บไว้แค่อันเดียว กุญแจสำคัญคือต้องใช้ของจริงที่ไม่ซ้ำกันเป็น ID เช่น stripe_sub_id ไม่ใช่สุ่มเลข
ทราบได้ยังไงว่า Facebook tracking ของเรามีรูรั่วหรือเปล่า
ให้เทียบยอด Purchase ใน Facebook Ads Manager กับยอดจริงใน payment provider ของคุณครับ ถ้าห่างกันเกิน 10-20% แปลว่า tracking มีรูรั่ว นอกจากนี้ Facebook Events Manager ยังมี Event Match Quality score ที่บอกว่า event ที่ส่งไปมีข้อมูล match กับ user ได้แม่นแค่ไหน
ถ้าคุณก็อยากมี AI Agent แบบนี้ — ตัวที่อยู่บน server ส่วนตัวของคุณ คอยดูแลเว็บ ดูแลระบบ tracking ไล่ปิดรูรั่วที่คุณมองไม่เห็น แก้ปัญหาเทคนิคที่คุณไม่อยากแตะเอง — ตอนนี้Newton เปิดให้ลงทะเบียนแล้วครับ เซ็ตให้เสร็จภายใน 10 นาที ได้ AI Agent ส่วนตัวที่ทำงานแบบทิมไว้ใช้เอง ลองไปดูได้ที่หน้า Newton เลย
— ปอนด์
