Sync WhatsApp Contact Names With contact.updated Webhooks
To keep a shared inbox’s WhatsApp contact names current, process UnifyPort’s contact.updated as a partial address-book update, not a replacement contact record. Apply supplied name strings, preserve omitted fields, and treat an empty string as an explicit clear. Reject null. Keep the contact resource identifier separate from the conversation identifier, and do not overwrite an agent’s local nickname or the connected account’s profile.
Key takeaways
- This documented event concerns
provider: whatsapp, notwhatsapp-protocolor every channel in a unified inbox. - A missing name and an empty name have different meanings.
- Store address-book names separately from local aliases and account profile names.
- Authenticate and persist the event before updating the inbox projection.
Which name changed?
WhatsApp’s official contact-management announcement describes managing contacts from linked-device surfaces. That provides context for why a stored name can change outside your support application; it does not define the UnifyPort event or promise that every edit emits it.
The UnifyPort standard event reference defines contact.updated for WhatsApp address-book name changes. This is different from adding a person or sending their details to someone else. Use the add contact vs send vCard guide for those outbound operations.
| Data | Meaning | Recommended storage boundary |
|---|---|---|
data.contact.id | Contact resource identifier | Key the contact within its workspace, provider, and messaging account |
data.contact.conversation_id | Associated chat identifier when supplied | Store as an explicit mapping, not a value derived from the contact ID |
data.contact.address_book | Supplied address-book name fields | Merge only the supplied supported fields |
| Local agent nickname | Your application’s chosen label | Keep separate; do not overwrite it from this event |
account.profile.updated | Connected account’s own public name profile | Route to a different handler |
A customer contact rename is not a business account rename. Neither is a message edit.
Read the payload as a patch
This illustrative payload follows the documented contract; the identifiers and names are examples, not a captured customer event:
{
"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"
}
}
}
Use values actually present in address_book. Do not turn a name listed in changed_fields but absent from the object into a deletion, or substitute a missing value with "".
| Incoming field | Action |
|---|---|
| Non-empty string | Set that address-book field |
"" | Clear that field |
| Omitted | Preserve the previous value |
null or another non-string value | Reject the patch for validation review |
The following JavaScript is only a merge helper for the two documented name fields. It is not a complete webhook receiver, identity resolver, or event-ordering implementation:
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;
}
Validation happens before applying either field, so a malformed patch does not leave a half-updated name. Unknown fields are not copied into the projection; retain the authenticated event separately if your retention policy permits later schema review.
Build the receiver around field-level state
- Subscribe deliberately. Add
contact.updatedwithout dropping other required subscriptions. The event-filter guide explains explicit lists and wildcard collectors. Keep the existing signing configuration intact. - Verify and persist. Follow the webhook delivery contract: verify HMAC-SHA256 over
X-Device-Timestamp, a dot, and the raw body usingsigning_secret; check timestamp freshness. Durably store authenticated events before acknowledging with 2xx. Quarantine authenticated schema-invalid events for review rather than silently applying them. - Resolve identity. Dispatch on event type and provider. Scope
data.contact.idby workspace, provider, andaccount_id. Save an explicitly suppliedconversation_id; never manufacture a chat identifier from a phone number or contact ID. A missing mapping must not merge unrelated records. - Handle repeats and ordering. Deduplicate ordinary deliveries by event ID within the workspace. Delivery order is not guaranteed. A recommended projection policy is to track the latest applied
occurred_atand event-ID tiebreaker per name field, not only per contact. An older event may contain a field that a newer partial event never touched. This is local bookkeeping, not an extra API field or a claim of perfect upstream causal ordering. - Render with provenance. Choose an explicit display policy, such as local nickname first, then address-book full name. After a clear, recompute the display from the remaining permitted sources; do not restore the cleared value from a stale cache.
Apply deduplication, field-version decisions, and the projection mutation transactionally or through a serialized worker. Merely setting a processed flag before a database write can lose the update after a crash.
Acceptance checks and boundaries
Test a full-name-only patch, a first-name-only patch, an empty-string clear, an omitted field, invalid null, duplicate delivery, out-of-order partial patches, and the same contact identifier under different messaging accounts. Also confirm that local nicknames and account profiles remain unchanged. These are proposed tests, not reported production results.
UnifyPort is an unofficial interface. The event is not a complete address-book snapshot, guaranteed replay, or a contact-deletion signal. Do not delete a contact because a name was cleared. A valid subscription does not guarantee every upstream change will arrive; show stale or uncertain state honestly and verify behavior with the connected account before relying on it.
FAQ
Does an omitted full_name mean the name was removed?
No. Preserve it. Only an explicitly supplied empty string clears that field.
Can I use contact.id as the destination for a reply?
Do not assume equivalence. Contact and conversation identifiers serve different resources. Use the documented conversation mapping for chat operations.
Does this synchronize LINE or Zalo contact names too?
No such support is documented for this event. A shared envelope does not imply identical channel capabilities.
Next step and sources
Review the standard event contract, then test the merge rules against a controlled WhatsApp contact before enabling inbox updates.
References checked on 2026-10-02:
Turn messaging integration into a stable product pipeline.
Start by sending through one API, then bring every inbound message back into your business system with standard events.