← บทความทั้งหมด
บทช่วยสอน

ซิงก์ชื่อผู้ติดต่อ WhatsApp ด้วย Webhook contact.updated

หากต้องการให้ชื่อผู้ติดต่อ WhatsApp ในกล่องข้อความร่วมเป็นปัจจุบัน ให้ประมวลผล contact.updated ของ UnifyPort เป็นการอัปเดตสมุดรายชื่อบางส่วน ไม่ใช่การแทนที่ข้อมูลผู้ติดต่อทั้งรายการ ใช้เฉพาะสตริงชื่อที่ส่งมา เก็บฟิลด์ที่ไม่ได้ส่งไว้ตามเดิม และถือว่าสตริงว่างเป็นคำสั่งล้างค่า ส่วน null เป็นค่าที่ไม่ถูกต้อง ต้องแยก ID ผู้ติดต่อออกจาก ID บทสนทนา และไม่เขียนทับชื่อเล่นที่เจ้าหน้าที่ตั้งไว้หรือโปรไฟล์ของบัญชีรับส่งข้อความที่เชื่อมต่อ

ประเด็นสำคัญ

  • เอกสารระบุอีเวนต์นี้สำหรับ provider: whatsapp ไม่รวม whatsapp-protocol และไม่ได้หมายถึงทุกช่องทาง
  • ฟิลด์ชื่อที่หายไปกับชื่อที่เป็นค่าว่างมีความหมายต่างกัน
  • แยกชื่อในสมุดรายชื่อ ชื่อเรียกภายใน และชื่อโปรไฟล์ของบัญชีออกจากกัน
  • ตรวจสอบลายเซ็นและบันทึกอีเวนต์อย่างถาวรก่อนปรับข้อมูลที่ใช้แสดงผล

ชื่อของใครเปลี่ยนไป?

ประกาศอย่างเป็นทางการเรื่องการจัดการผู้ติดต่อของ WhatsApp อธิบายแนวทางจัดการผู้ติดต่อจากอุปกรณ์ที่เชื่อมโยง นี่เป็นบริบทว่าทำไมชื่ออาจเปลี่ยนจากนอกแอปงานบริการ แต่ประกาศนั้นไม่ได้กำหนด สัญญาอีเวนต์ของ UnifyPort หรือรับประกันว่าทุกการแก้ไขจะส่งอีเวนต์

เอกสารอีเวนต์มาตรฐานของ UnifyPort ระบุว่า contact.updated คือการเปลี่ยนชื่อในสมุดรายชื่อ WhatsApp ไม่ใช่การเพิ่มผู้ติดต่อหรือส่งรายละเอียดให้ผู้อื่น หากต้องการทำสองอย่างนั้น ให้ดูคู่มือเพิ่มผู้ติดต่อเทียบกับส่ง vCard

ข้อมูลความหมายขอบเขตการจัดเก็บที่แนะนำ
data.contact.idตัวระบุทรัพยากรผู้ติดต่อใช้ร่วมกับ workspace, provider และบัญชีรับส่งข้อความ
data.contact.conversation_idตัวระบุบทสนทนาที่เกี่ยวข้องเมื่อมีส่งมาเก็บเป็นความสัมพันธ์ที่ชัดเจน ไม่คำนวณจาก ID ผู้ติดต่อ
data.contact.address_bookฟิลด์ชื่อที่ส่งมาในครั้งนี้รวมเฉพาะฟิลด์ที่รองรับและมีอยู่จริง
ชื่อเล่นภายในของเจ้าหน้าที่ป้ายชื่อที่แอปกำหนดเองเก็บแยก ไม่ให้อีเวนต์นี้เขียนทับ
account.profile.updatedชื่อโปรไฟล์สาธารณะของบัญชีที่เชื่อมต่อเองส่งไปยังตัวประมวลผลอีกชุด

การเปลี่ยนชื่อผู้ติดต่อไม่ใช่การเปลี่ยนชื่อบัญชีธุรกิจหรือแก้ไขข้อความ แม้จะรวม LINE กับ WhatsApp ไว้ในหน้าจอเดียวกัน ก็ควรรักษาขอบเขตนี้

อ่าน payload เป็นการอัปเดตบางส่วน

ตัวอย่างนี้ใช้โครงสร้างตามเอกสาร ชื่อและตัวระบุเป็นข้อมูลสาธิต ไม่ใช่อีเวนต์จากลูกค้าจริง:

{
  "id": "0000000000000000000000000000000000000000000000000000000000000191",
  "type": "contact.updated",
  "provider": "whatsapp",
  "account_id": "acc_example",
  "occurred_at": "2026-09-20T03:00:00Z",
  "data": {
    "contact": {
      "id": "15550000002@s.whatsapp.net",
      "conversation_id": "100000000000002@lid",
      "address_book": {
        "full_name": "Example customer",
        "first_name": "Example"
      }
    },
    "event": {
      "kind": "contact_updated",
      "source": "address_book",
      "changed_fields": ["address_book.full_name", "address_book.first_name"],
      "changed_at": "2026-09-20T03:00:00Z"
    }
  }
}

ใช้ค่าที่มีอยู่จริงใน address_book หาก changed_fields ระบุชื่อฟิลด์แต่ไม่มีค่าในออบเจ็กต์ อย่าตีความว่าเป็นการลบหรือเติม "" ให้เอง

ค่าที่ได้รับการดำเนินการ
สตริงที่ไม่ว่างตั้งค่าฟิลด์นั้นในสมุดรายชื่อ
""ล้างฟิลด์นั้น
ไม่ส่งฟิลด์มาเก็บค่าเดิม
null หรือค่าที่ไม่ใช่สตริงไม่ใช้ patch และส่งไปตรวจสอบความถูกต้อง

JavaScript ต่อไปนี้เป็นเพียงตัวช่วยรวมสองฟิลด์ชื่อที่ระบุในเอกสาร ไม่ใช่ตัวรับ Webhook ที่ครบถ้วน ตัวแก้ความสัมพันธ์ของ ID หรือระบบจัดลำดับอีเวนต์:

function mergeAddressBook(current, patch) {
  if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
    throw new Error('Invalid address_book object');
  }
  const fields = ['full_name', 'first_name'];
  for (const field of fields) {
    if (Object.hasOwn(patch, field) && typeof patch[field] !== 'string') {
      throw new Error('Invalid address-book name');
    }
  }
  const next = { ...current };
  for (const field of fields) {
    if (Object.hasOwn(patch, field)) next[field] = patch[field];
  }
  return next;
}

ตรวจสอบทุกฟิลด์เป้าหมายก่อนเปลี่ยนค่าใด ๆ เพื่อไม่ให้ patch ที่ผิดรูปแบบอัปเดตชื่อไปเพียงครึ่งเดียว ฟิลด์ที่ไม่รู้จักจะไม่ถูกคัดลอกลงข้อมูลแสดงผล หากนโยบายเก็บข้อมูลอนุญาต ให้เก็บอีเวนต์ที่ผ่านการยืนยันแยกไว้สำหรับตรวจสอบ schema ภายหลัง

ออกแบบตัวรับด้วยสถานะระดับฟิลด์

  1. เลือกการสมัครรับให้ชัดเจน เพิ่ม contact.updated โดยไม่ลบอีเวนต์อื่นที่จำเป็น คู่มือตัวกรองอีเวนต์ อธิบายรายการแบบระบุชื่อกับ wildcard และควรเก็บการตั้งค่าลายเซ็นเดิมไว้
  2. ตรวจสอบและบันทึก ปฏิบัติตามสัญญาการส่ง Webhook ใช้ signing_secret ตรวจ HMAC-SHA256 ของ X-Device-Timestamp ตามด้วยจุดและเนื้อหา request แบบ raw ตรวจอายุ timestamp แล้วบันทึกอีเวนต์ที่ยืนยันแล้วอย่างถาวรก่อนตอบ 2xx หากลายเซ็นถูกต้องแต่โครงสร้างผิด ให้แยกไว้ตรวจสอบ ไม่ใช่นำไปใช้เงียบ ๆ
  3. แยกตัวตน ส่งต่ออีเวนต์ตามชนิดและ provider จำกัดขอบเขต data.contact.id ด้วย workspace, provider และ account_id เก็บ conversation_id เฉพาะเมื่อส่งมาอย่างชัดเจน อย่าสร้าง ID บทสนทนาจากเบอร์โทรหรือ ID ผู้ติดต่อ และอย่ารวมข้อมูลที่ไม่เกี่ยวกันเมื่อยังไม่มี mapping
  4. รองรับการซ้ำและผิดลำดับ สำหรับอีเวนต์ทั่วไป ให้ตัดรายการซ้ำด้วย event ID ภายใน workspace ไม่มีการรับประกันลำดับการส่ง แนะนำให้เก็บ occurred_at ล่าสุดที่ใช้และ event ID สำหรับตัดสินเมื่อเวลาเท่ากัน แยกตามแต่ละฟิลด์ชื่อ ไม่ใช่ทั้งผู้ติดต่อ เพราะอีเวนต์เก่าอาจมีฟิลด์ที่อีเวนต์ใหม่ไม่ได้แตะ นี่คือนโยบายบันทึกภายในแอป ไม่ใช่ฟิลด์ API เพิ่มเติมหรือการรับประกันลำดับเหตุการณ์จริงจากต้นทาง
  5. กำหนดที่มาของชื่อแสดงผล เช่น ใช้ชื่อเล่นภายในก่อน แล้วจึงใช้ชื่อเต็มในสมุดรายชื่อ หลังล้างค่า ให้คำนวณชื่อใหม่จากแหล่งที่ยังอนุญาต อย่านำค่าที่เพิ่งล้างกลับมาจาก cache เก่า

ทำการตัดซ้ำ ตรวจเวอร์ชันระดับฟิลด์ และปรับข้อมูลแสดงผลใน transaction เดียวหรือ worker ที่ทำงานตามลำดับ การตั้งธงว่าประมวลผลแล้วก่อนเขียนฐานข้อมูลอาจทำให้อัปเดตสูญหายเมื่อระบบหยุดกลางทาง

การทดสอบและข้อจำกัด

ทดสอบ patch ที่มีเฉพาะชื่อเต็ม เฉพาะชื่อแรก การล้างด้วยสตริงว่าง ฟิลด์ที่ไม่ส่งมา null ที่ไม่ถูกต้อง การส่งซ้ำ patch บางส่วนที่มาผิดลำดับ และ ID ผู้ติดต่อเดียวกันในคนละบัญชีรับส่งข้อความ ตรวจด้วยว่าชื่อเล่นภายในและโปรไฟล์บัญชีไม่เปลี่ยน นี่คือกรณีทดสอบที่เสนอ ไม่ใช่ผลทดสอบ production

UnifyPort เป็นอินเทอร์เฟซที่ไม่เป็นทางการ อีเวนต์นี้ไม่ใช่ snapshot สมุดรายชื่อทั้งหมด ไม่รับประกัน replay และไม่ใช่สัญญาณลบผู้ติดต่อ อย่าลบผู้ติดต่อเพียงเพราะชื่อถูกล้าง การสมัครรับที่ถูกต้องก็ไม่ได้รับประกันว่าทุกการเปลี่ยนจากต้นทางจะมาถึง ควรแสดงสถานะเก่าหรือยังไม่ยืนยันตามจริง และตรวจพฤติกรรมกับบัญชีที่เชื่อมต่อก่อนใช้งานเป็นหลัก

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

ไม่มี full_name หมายถึงลบชื่อหรือไม่?

ไม่ใช่ ให้เก็บค่าเดิม มีเพียงสตริงว่างที่ส่งมาอย่างชัดเจนเท่านั้นที่ล้างฟิลด์นี้

ใช้ contact.id เป็นปลายทางตอบข้อความได้เลยหรือไม่?

อย่าถือว่าเท่ากับ ID บทสนทนา ทั้งสองระบุคนละทรัพยากร ให้ใช้ mapping บทสนทนาตามเอกสารสำหรับงานแชท

ใช้ซิงก์ชื่อใน LINE หรือ Zalo ได้ด้วยหรือไม่?

อีเวนต์นี้ไม่มีเอกสารระบุการรองรับดังกล่าว envelope ที่เหมือนกันไม่ได้หมายถึงความสามารถทุกช่องทางเหมือนกัน

ขั้นตอนถัดไปและแหล่งอ้างอิง

อ่านสัญญาอีเวนต์มาตรฐาน แล้วทดสอบกฎการรวมกับผู้ติดต่อ WhatsApp ที่ใช้ทดสอบ ก่อนเปิดการอัปเดตในกล่องข้อความ

ตรวจสอบแหล่งข้อมูลเมื่อ 2026-10-02:

UnifyPort API

เปลี่ยนการเชื่อมต่อข้อความให้เป็น pipeline ผลิตภัณฑ์ที่เสถียร

เริ่มจากการส่งผ่าน API เดียว แล้วส่งข้อความขาเข้าทั้งหมดกลับสู่ระบบธุรกิจของคุณด้วย event มาตรฐาน