Skip to main content

Contact Deduplicated Event

Triggered when duplicate contacts are merged due to LID (Local Identifier) to PN (Phone Number) mapping discovery. This happens when WhatsApp reveals that a LID belongs to a phone number that already exists as a separate contact.

Event Type

contact-deduplicated

Payload Structure

{
"event": "contact-deduplicated",
"instanceId": "instance-uuid",
"data": {
"keptContactId": "contact-uuid-kept",
"deletedContactId": "contact-uuid-deleted",
"lid": "LID123@lid",
"pn": "5511888888888@s.whatsapp.net",
"messagesReassigned": 15,
"timestamp": "2024-12-14T12:00:00.000Z"
}
}

Data Fields

FieldTypeDescription
keptContactIdstringID of the contact that was kept (merged into)
deletedContactIdstringID of the contact that was deleted (merged from)
lidstringThe LID that triggered the deduplication
pnstringThe phone number JID associated with the LID
messagesReassignednumber | undefinedNumber of messages reassigned to the kept contact
timestampstringISO 8601 timestamp of the deduplication

Why Deduplication Happens

WhatsApp uses two types of identifiers:

  1. JID (Phone Number): Traditional identifier like 5511999999999@s.whatsapp.net
  2. LID (Local ID): Privacy-focused identifier that hides the phone number

When you first interact with a contact, you might only see their LID. Later, WhatsApp may reveal that this LID maps to a phone number. If you already have a contact with that phone number, the system merges them to avoid duplicates.

Before deduplication:
├── Contact A (JID: 5511999999999@s.whatsapp.net)
└── Contact B (LID: LID123@lid)

WhatsApp reveals: LID123 = 5511999999999

After deduplication:
└── Contact A (JID: 5511999999999@s.whatsapp.net, LID: LID123@lid)
└── All messages from Contact B reassigned here

Example: Handling Deduplication

app.post('/webhook', async (req, res) => {
const { event, data } = req.body;

if (event === 'contact-deduplicated') {
const { keptContactId, deletedContactId, messagesReassigned } = data;

// Update your local database to reflect the merge
await db.transaction(async (tx) => {
// Reassign any local references from deleted to kept contact
await tx.conversations.update(
{ contactId: deletedContactId },
{ contactId: keptContactId }
);

await tx.messages.update(
{ contactId: deletedContactId },
{ contactId: keptContactId }
);

// Delete the duplicate contact record
await tx.contacts.delete({ externalId: deletedContactId });
});

console.log(
`Merged contact ${deletedContactId} into ${keptContactId}. ` +
`${messagesReassigned || 0} messages reassigned.`
);
}

res.status(200).send('OK');
});

Important Considerations

Update Your References

When you receive this event, make sure to update any references to deletedContactId in your system to point to keptContactId instead. The deleted contact no longer exists in Zapy.

  • Message History: All messages from the deleted contact are automatically reassigned to the kept contact in Zapy
  • Chat Continuity: The conversation continues seamlessly under the kept contact
  • Data Integrity: The messagesReassigned field helps you verify the merge was complete