เช้านี้ผมเปิด admin dashboard ของ Newton ดูเหมือนทุกวัน — แล้วเอ๊ะ ทำไมตัวเลข trial conversion ไม่ขยับเลย ทั้งที่ผมรู้อยู่แก่ใจว่ามีลูกค้า 2 คนเพิ่งหมด trial ไปเมื่อวานซืน
"ทิม ช่วยเช็คให้หน่อย ลูกค้า 2 คนนี้น่าจะ convert เป็น paid แล้วนะ แต่ dashboard ยังขึ้น trial อยู่"
ใน 1 ชั่วโมงต่อมา — เจอ root cause, แก้ที่ Stripe, backfill ลูกค้าทั้ง 2 คนกลับเข้าระบบ, แถมเพิ่ม feature ใหม่ให้ admin อีกอันให้ครบในตัว
เรื่องนี้เป็นตัวอย่างชัดๆ ว่าการมี AI Agent ที่อยู่บนเซิร์ฟเวอร์ของตัวเอง + เห็น Stripe API + เห็น database + เห็น code ทั้งระบบ มันต่างกับ ChatGPT ยังไง
อาการ — ตัวเลขไม่ตรงกับสิ่งที่ Stripe เห็น
Newton เป็น บริการ managed AI server ครับ ลูกค้าจ่ายผ่าน Stripe เริ่มที่ 7 วัน trial ฟรี แล้วถึงตัด 990 บาท/เดือน อัตโนมัติ
Workflow ปกติคือ:
- ลูกค้าใส่บัตรเข้ามา → trial เริ่ม
- ครบ 7 วัน → Stripe สร้าง invoice แล้วตัดบัตร
- Stripe ยิง webhook
invoice.paidมาที่ Newton - Newton เปลี่ยน
is_trial=0,status=active, ส่งอีเมล thank-you, อัปเดตลิสต์อีเมล Brevo - Admin dashboard เห็นตัวเลข trial converted +1
แต่เช้านี้ผมเปิด dashboard ดู — trial conversion 30 วันยัง 1 เหมือนเดิม ทั้งที่ผมรู้ว่ามี 2 คนหมด trial ไปแล้ว
Stripe dashboard ก็แสดงว่าเก็บเงินสำเร็จทั้ง 2 ราย รวม 1,980 บาทเข้าบัญชีจริง
แปลว่าเงินเข้าจริง แต่ระบบฝั่งผมไม่รู้ว่าลูกค้าจ่ายแล้ว 555
ทิมเริ่มไล่จาก Stripe API
ผมไม่ได้บอกทิมว่าจะให้เริ่มจากไหน — แค่บอกว่า "ลูกค้า 2 คนนี้จ่ายแล้วนะ ทำไม dashboard ไม่ขึ้น"
ทิมเริ่มจาก Stripe API ก่อนเพราะนั่นคือ source of truth ของเรื่องการจ่ายเงิน:
- Query subscription ของลูกค้าทั้ง 2 ราย → status เป็น
active(ไม่ใช่trialingแล้ว) - Query invoices → ทั้งคู่มี invoice ที่
paid: trueเมื่อ 1-2 วันก่อน - เงินเข้าจริง — confirmed
OK งั้นปัญหาอยู่ฝั่ง Newton ที่ไม่ได้รับ event หรือรับแล้วประมวลผลพลาด
ทิมเปิดดู webhook endpoint config บน Stripe ผ่าน API:
GET /v1/webhook_endpoints/{id}
แล้ว enabled_events ที่ subscribe ไว้มีแค่ 4 ตัว:
checkout.session.completedcustomer.subscription.deletedcustomer.subscription.updatedinvoice.payment_failed
...ไม่มี invoice.paid
แต่ไปดูใน main.py ของ Newton — handler สำหรับ invoice.paid เขียนไว้แล้วเรียบร้อย logic ทำงานถูกต้องเป๊ะ พร้อม convert trial → paid + ส่งอีเมล + sync Brevo + ทุกอย่าง
ปัญหาคือ Stripe ไม่เคยส่ง event ตัวนี้มาเลย เพราะเราไม่ได้ subscribe มันบน endpoint
Code มีหมด แต่ "ก๊อกน้ำ" ปิดอยู่
Bug นี้น่ารักตรงที่มัน "เงียบ"
ที่น่ากลัวของ bug แบบนี้คือ ไม่มีใครจะแจ้ง error เลย
- Stripe ไม่ขึ้น error เพราะมัน follow config เป๊ะ — เราไม่ subscribe มันก็ไม่ส่ง
- Newton ไม่ขึ้น error เพราะไม่ได้ฟัง event — handler ก็ไม่ได้ถูกเรียกเลย
- Admin dashboard ก็ไม่ขึ้น error เพราะ DB ก็ตรงกับสิ่งที่ตัวมันเห็น
มันเงียบทุกชั้น
ถ้าผมไม่บังเอิญเปิด dashboard ดูเอง แล้วสะดุดใจว่าตัวเลขมันไม่ตรง — bug ตัวนี้อาจอยู่นานเป็นเดือน เก็บเงินเข้าจริงเรื่อยๆ แต่ลูกค้าทุกคนจะค้างเป็น trial ตลอดไปในระบบของผม
นี่คือคลาสสิก distributed system bug ที่ผมเขียนถึงในกรณี Brevo email sync — event-driven system มันเปราะเสมอ ถ้า event ตัวเดียวพลาด ระบบก็จะเดินผิด trace กันต่อไปเรื่อยๆ
3 fix ที่ทิมลงมือทำเองในชั่วโมงเดียว
Fix 1: เพิ่ม invoice.paid ใน Stripe webhook
ทิมเรียก Stripe API ตรงๆ เลย:
POST /v1/webhook_endpoints/{id} พร้อม enabled_events array ที่รวม invoice.paid เพิ่มเข้าไปด้วย
ตั้งแต่วินาทีนี้ทุก invoice ที่จ่ายผ่านบนคนต่อไป — Stripe จะส่ง event มา handler ก็ทำงานเอง
Fix 2: Backfill ลูกค้า 2 คนที่ระบบพลาดไป
ทิมเขียน script backfill_trial_conversions.py ที่ import main.py ของ Newton ตรงๆ แล้วเรียก helper functions เดียวกับที่ webhook handler ใช้:
- UPDATE subs ใน DB (is_trial=0, status=active)
- เขียน audit_log ว่า trial_converted (พร้อม flag
backfilled: trueกันสับสนตอนรีวิว log ภายหลัง) - ส่ง Telegram noti บอกผม
- ส่งอีเมล thank-you ให้ลูกค้า
- ย้ายลูกค้าจาก Brevo trial list → paid list
- ยิง Facebook CAPI Purchase event เพื่อให้ FB Ads รู้ว่าคน 2 คนนี้ convert แล้ว (สำคัญมากกับ การ optimize ad campaign — และเป็น event ตัวเดียวกับที่ผมให้ทิมต่อ server-side ทั้งระบบ ตอน Facebook นับ Purchase ขาดไปเกือบครึ่ง)
เรียกใช้ helper เดิมแทนเขียนใหม่ — แปลว่าถ้า logic webhook ปกติทำงานถูก backfill ก็ทำงานถูกเหมือนกัน ไม่มีทาง drift
ที่ผมชอบคือ ก่อน backfill ทิม backup DB ไว้ก่อนที่ /tmp/newton.db.backup-XXXXX เผื่อทำพลาดแล้วต้อง revert (นิสัยเดียวกับตอนเคสที่ผมอัปโหลดทับข้อมูลบัญชีหายเอง — snapshot ก่อนเสมอ ถึงเรียกตัวเองว่ามืออาชีพได้)
Fix 3: Auto-refresh dashboard ทุก 30 วินาที
ทิมพูดขึ้นมาเองว่า "เดี๋ยวก่อนพี่ปอนด์ ผมเจออีก issue แทรก — ถึงผมจะแก้ webhook แล้ว แต่หน้า admin.html มันยัง loadDashboard() แค่ตอนเปิดหน้าเท่านั้น ถ้าพี่เปิดทิ้งไว้แล้วลูกค้าจ่ายเงินกลางทาง พี่ก็ยังไม่เห็น"
เลยใส่ auto-refresh ทุก 30 วินาที ใน admin.html ทั้งฝั่งไทยและฝั่งอังกฤษ — แบบ visibility-aware (ไม่ refresh ถ้า tab ไม่ active หรือ document hidden, กันยิง API ฟรีตอนแล็ปท็อปปิดอยู่)
(ตอนทิมแก้ webhook เสร็จผมยังเปิดเจอตัวเลขใน dashboard ขัดแย้งกันเองอีกชุด — ลูกค้า 11 vs server 12 — เลยสั่ง refactor endpoint ทั้งก้อนเป็น 5-stage funnel ต่อเลยในรอบเดียวกัน)
นี่คือสิ่งที่ผมเห็นค่ามากของการมี AI Agent ไม่ใช่ AI Chatbot — มันไม่ได้แค่แก้สิ่งที่ผมขอ มันมองภาพรวมแล้ว เสนอ fix ที่เกี่ยวข้องเอง
ผลลัพธ์ — verify ผ่าน live API
หลัง backfill เสร็จ ทิมเรียก /api/stats live เลย:
- trial_converted_30d: 1 → 3
- trial_active: 3 → 1
- MRR: +1,980 บาท (= 2 × 990)
- Conversion rate 30 วัน: 75%
ตัวเลขตรงกับ Stripe เป๊ะ — ระบบกลับมา consistent อีกครั้ง
ลูกค้าทั้ง 2 คนได้รับเมล thank-you ที่ค้างไว้ + ถูกย้ายเข้า paid email list (จะได้ไม่ได้รับเมล "trial หมดเร็ว ๆ นี้" อีก — กรณีเดียวกันที่ผมเขียนไว้ใน Brevo sync rewrite)
FB Ads ก็ได้ Purchase event ครบ 2 events เพื่อ optimize campaign
บทเรียนที่ผมจดไว้
1. Code มี handler ไม่พอ — ต้อง subscribe event บน Stripe ด้วย
เวลาเพิ่ม webhook handler ใน code ใหม่ ทุกครั้งต้อง verify enabled_events ของ Stripe endpoint ด้วย — มันคนละชั้นกัน
ทิมจดเป็น lesson ใน memory.md เลยว่า "ทุกครั้งที่เพิ่ม Stripe webhook handler ใน code ใหม่ → ต้อง verify enabled_events บน endpoint ด้วย"
2. Event-driven system ต้องมี reconciliation
Event-driven design มัน fragile โดยธรรมชาติ ดังนั้นทุกระบบที่อาศัย event ควรมี cron self-heal คอยเทียบ state กับ source of truth (กรณีนี้คือ Stripe) — ผมจดไว้ว่ารอบหน้าจะให้ทิมเขียน cron daily reconcile DB Newton กับ Stripe ด้วยเลย
3. Backfill ผ่าน helper เดิม ไม่เขียนใหม่
ถ้า logic ปกติถูกแล้ว backfill = เรียก function เดียวกันแค่เพิ่ม flag backfilled: true ใน audit log เท่านั้น ห้ามเขียน logic แยก ไม่งั้นจะ drift กับ webhook ปกติ
ทำไมเรื่องนี้ถึงเล่าออกมาให้ฟัง
เพราะมันสะท้อน 3 อย่างที่ผมรู้สึกว่าคนทำธุรกิจส่วนตัวควรรู้ครับ
หนึ่ง — ระบบที่คุณซื้อจาก SaaS ไม่ใช่ทุกอย่างทำงานเป๊ะแบบที่โฆษณาบอก โดยเฉพาะการเชื่อมหลาย service เข้าด้วยกัน รอย bug แบบนี้เจอได้ตลอดเวลา
สอง — ถ้าคุณไม่มี AI Agent ที่เห็น code, DB, API, Stripe พร้อมๆ กัน — bug แบบนี้จะใช้เวลาแกะเป็นวันหรือเป็นสัปดาห์ ทิมใช้เวลา 1 ชั่วโมง ตั้งแต่ผมพิมพ์คำถามจน live verify เสร็จ
สาม — bug ที่เงียบที่สุดคือ bug ที่อันตรายที่สุด เพราะคุณจะไม่รู้ตัวจนกระทั่งดู metric ผิดสะดุดตา ดังนั้น admin dashboard ที่ดี (ที่ทิมสร้างให้ผมในบ่ายเดียว) คือสิ่งที่มีค่ามากกว่า feature ใหม่อีก 10 อัน
คำถามที่พบบ่อย
Stripe webhook ไม่ทำงาน ต้องเช็คอะไรบ้าง?
เช็ค 2 ชั้นครับ ชั้นแรกคือโค้ดของเรา ว่ามี handler สำหรับ event นั้นไหม ชั้นที่สองคือ Stripe webhook endpoint ว่า subscribe event นั้นไว้หรือยัง ทั้งสองต้องตรงกัน มี handler แต่ไม่ subscribe → Stripe ไม่ส่ง event มาเลย subscribe แต่ไม่มี handler → ได้รับแต่ไม่ประมวลผล
bug ที่เงียบแบบ Stripe webhook พลาด จะจับได้ยังไงก่อนสายเกินไป?
ต้องมี dashboard ที่ดูตัวเลข business จริงครับ ไม่ใช่แค่ดู error log เพราะ bug แบบนี้ทุกระบบ silent ไม่มีใคร throw error เลย วิธีจับคือดู metric และถ้ามีตัวเลขที่ไม่ตรงกับความคาดหวัง เช่น ลูกค้าควรจะ convert แล้วแต่ไม่ขึ้น นั่นคือสัญญาณให้เริ่มขุด
event-driven system ทำไมถึง fragile ควรป้องกันยังไง?
เพราะถ้า event ตัวใดตัวหนึ่งหลุด ระบบทั้งสายก็ drift ออกจาก state ที่ถูกต้องโดยไม่มีใครรู้ครับ วิธีป้องกันคือมี reconciliation cron ที่เปรียบเทียบ state ของเรากับ source of truth เช่น compare DB กับ Stripe API ทุกวัน ถ้ามีความขัดแย้งก็ self-heal กลับให้ถูก
backfill ข้อมูลที่ระบบ miss ไปต้องทำยังไง ไม่ให้ logic drift จากระบบปกติ?
ใช้ helper function เดียวกับที่ webhook handler ใช้ครับ ไม่เขียน logic ใหม่แยก เพียงแต่เพิ่ม flag backfilled: true ใน audit log เพื่อให้รู้ว่า record นี้มาจาก backfill ไม่ใช่ flow ปกติ วิธีนี้การันตีว่า backfill ถูกต้องเหมือน webhook จริงๆ ไม่มีทาง drift
ลองมี AI Agent บนเซิร์ฟเวอร์ของตัวเองดูครับ
ตอนผมเล่าเรื่องนี้ให้เพื่อนฟัง เพื่อนถามว่า "แล้วถ้าผมไม่ใช่เดฟล่ะ ผมจะมี AI Agent ที่ทำแบบนี้ได้ยังไง?"
ตอบเลยว่า — Newton ครับ newton.incomeinclick.in.th
คุณได้ AI Agent ส่วนตัว + เซิร์ฟเวอร์ของตัวเอง พร้อมใช้ใน 10 นาที — ลองฟรี 7 วัน ไม่ใช้ก็ยกเลิกได้ ไม่มีค่าใช้จ่าย AI Agent ของคุณจะอยู่บนเซิร์ฟเวอร์ของคุณคนเดียว เห็น code ของคุณ เห็น DB ของคุณ เห็น API ที่คุณใช้ — และพร้อมจะแกะ bug แปลกๆ แบบนี้ให้คุณเหมือนที่ทิมแกะให้ผม
(และถ้าวันไหนระบบของผมพลาดไม่ส่ง event ให้ Stripe เก็บเงินคุณตรงตามรอบ — ก็มี grace period 24 ชั่วโมงให้คุณแก้บัตรก่อน server หายเสมอครับ 555)
— ปอนด์
