ซิงก์ชื่อผู้ติดต่อ 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 ภายหลัง
ออกแบบตัวรับด้วยสถานะระดับฟิลด์
- เลือกการสมัครรับให้ชัดเจน เพิ่ม
contact.updatedโดยไม่ลบอีเวนต์อื่นที่จำเป็น คู่มือตัวกรองอีเวนต์ อธิบายรายการแบบระบุชื่อกับ wildcard และควรเก็บการตั้งค่าลายเซ็นเดิมไว้ - ตรวจสอบและบันทึก ปฏิบัติตามสัญญาการส่ง Webhook ใช้
signing_secretตรวจ HMAC-SHA256 ของX-Device-Timestampตามด้วยจุดและเนื้อหา request แบบ raw ตรวจอายุ timestamp แล้วบันทึกอีเวนต์ที่ยืนยันแล้วอย่างถาวรก่อนตอบ 2xx หากลายเซ็นถูกต้องแต่โครงสร้างผิด ให้แยกไว้ตรวจสอบ ไม่ใช่นำไปใช้เงียบ ๆ - แยกตัวตน ส่งต่ออีเวนต์ตามชนิดและ provider จำกัดขอบเขต
data.contact.idด้วย workspace, provider และaccount_idเก็บconversation_idเฉพาะเมื่อส่งมาอย่างชัดเจน อย่าสร้าง ID บทสนทนาจากเบอร์โทรหรือ ID ผู้ติดต่อ และอย่ารวมข้อมูลที่ไม่เกี่ยวกันเมื่อยังไม่มี mapping - รองรับการซ้ำและผิดลำดับ สำหรับอีเวนต์ทั่วไป ให้ตัดรายการซ้ำด้วย event ID ภายใน workspace ไม่มีการรับประกันลำดับการส่ง แนะนำให้เก็บ
occurred_atล่าสุดที่ใช้และ event ID สำหรับตัดสินเมื่อเวลาเท่ากัน แยกตามแต่ละฟิลด์ชื่อ ไม่ใช่ทั้งผู้ติดต่อ เพราะอีเวนต์เก่าอาจมีฟิลด์ที่อีเวนต์ใหม่ไม่ได้แตะ นี่คือนโยบายบันทึกภายในแอป ไม่ใช่ฟิลด์ API เพิ่มเติมหรือการรับประกันลำดับเหตุการณ์จริงจากต้นทาง - กำหนดที่มาของชื่อแสดงผล เช่น ใช้ชื่อเล่นภายในก่อน แล้วจึงใช้ชื่อเต็มในสมุดรายชื่อ หลังล้างค่า ให้คำนวณชื่อใหม่จากแหล่งที่ยังอนุญาต อย่านำค่าที่เพิ่งล้างกลับมาจาก cache เก่า
ทำการตัดซ้ำ ตรวจเวอร์ชันระดับฟิลด์ และปรับข้อมูลแสดงผลใน transaction เดียวหรือ worker ที่ทำงานตามลำดับ การตั้งธงว่าประมวลผลแล้วก่อนเขียนฐานข้อมูลอาจทำให้อัปเดตสูญหายเมื่อระบบหยุดกลางทาง
การทดสอบและข้อจำกัด
ทดสอบ patch ที่มีเฉพาะชื่อเต็ม เฉพาะชื่อแรก การล้างด้วยสตริงว่าง ฟิลด์ที่ไม่ส่งมา null ที่ไม่ถูกต้อง การส่งซ้ำ patch บางส่วนที่มาผิดลำดับ และ ID ผู้ติดต่อเดียวกันในคนละบัญชีรับส่งข้อความ ตรวจด้วยว่าชื่อเล่นภายในและโปรไฟล์บัญชีไม่เปลี่ยน นี่คือกรณีทดสอบที่เสนอ ไม่ใช่ผลทดสอบ production
UnifyPort เป็นอินเทอร์เฟซที่ไม่เป็นทางการ อีเวนต์นี้ไม่ใช่ snapshot สมุดรายชื่อทั้งหมด ไม่รับประกัน replay และไม่ใช่สัญญาณลบผู้ติดต่อ อย่าลบผู้ติดต่อเพียงเพราะชื่อถูกล้าง การสมัครรับที่ถูกต้องก็ไม่ได้รับประกันว่าทุกการเปลี่ยนจากต้นทางจะมาถึง ควรแสดงสถานะเก่าหรือยังไม่ยืนยันตามจริง และตรวจพฤติกรรมกับบัญชีที่เชื่อมต่อก่อนใช้งานเป็นหลัก
คำถามที่พบบ่อย
ไม่มี full_name หมายถึงลบชื่อหรือไม่?
ไม่ใช่ ให้เก็บค่าเดิม มีเพียงสตริงว่างที่ส่งมาอย่างชัดเจนเท่านั้นที่ล้างฟิลด์นี้
ใช้ contact.id เป็นปลายทางตอบข้อความได้เลยหรือไม่?
อย่าถือว่าเท่ากับ ID บทสนทนา ทั้งสองระบุคนละทรัพยากร ให้ใช้ mapping บทสนทนาตามเอกสารสำหรับงานแชท
ใช้ซิงก์ชื่อใน LINE หรือ Zalo ได้ด้วยหรือไม่?
อีเวนต์นี้ไม่มีเอกสารระบุการรองรับดังกล่าว envelope ที่เหมือนกันไม่ได้หมายถึงความสามารถทุกช่องทางเหมือนกัน
ขั้นตอนถัดไปและแหล่งอ้างอิง
อ่านสัญญาอีเวนต์มาตรฐาน แล้วทดสอบกฎการรวมกับผู้ติดต่อ WhatsApp ที่ใช้ทดสอบ ก่อนเปิดการอัปเดตในกล่องข้อความ
ตรวจสอบแหล่งข้อมูลเมื่อ 2026-10-02:
เปลี่ยนการเชื่อมต่อข้อความให้เป็น pipeline ผลิตภัณฑ์ที่เสถียร
เริ่มจากการส่งผ่าน API เดียว แล้วส่งข้อความขาเข้าทั้งหมดกลับสู่ระบบธุรกิจของคุณด้วย event มาตรฐาน