← All posts
Tutorial

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, not whatsapp-protocol or 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.

DataMeaningRecommended storage boundary
data.contact.idContact resource identifierKey the contact within its workspace, provider, and messaging account
data.contact.conversation_idAssociated chat identifier when suppliedStore as an explicit mapping, not a value derived from the contact ID
data.contact.address_bookSupplied address-book name fieldsMerge only the supplied supported fields
Local agent nicknameYour application’s chosen labelKeep separate; do not overwrite it from this event
account.profile.updatedConnected account’s own public name profileRoute 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 fieldAction
Non-empty stringSet that address-book field
""Clear that field
OmittedPreserve the previous value
null or another non-string valueReject 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

  1. Subscribe deliberately. Add contact.updated without dropping other required subscriptions. The event-filter guide explains explicit lists and wildcard collectors. Keep the existing signing configuration intact.
  2. Verify and persist. Follow the webhook delivery contract: verify HMAC-SHA256 over X-Device-Timestamp, a dot, and the raw body using signing_secret; check timestamp freshness. Durably store authenticated events before acknowledging with 2xx. Quarantine authenticated schema-invalid events for review rather than silently applying them.
  3. Resolve identity. Dispatch on event type and provider. Scope data.contact.id by workspace, provider, and account_id. Save an explicitly supplied conversation_id; never manufacture a chat identifier from a phone number or contact ID. A missing mapping must not merge unrelated records.
  4. 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_at and 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.
  5. 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:

UnifyPort API

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.