Documentation Index
Fetch the complete documentation index at: https://zapo.to/llms.txt
Use this file to discover all available pages before exploring further.
WhatsApp addresses users two ways, and zapo surfaces both. Understanding the distinction matters as soon as you touch groups, because WhatsApp is migrating group identities to LID for privacy.
PN vs LID
| Phone-number JID (PN) | LID |
|---|
| Form | 5511999999999@s.whatsapp.net | <opaque-id>@lid |
| Server suffix | @s.whatsapp.net | @lid |
| Reveals the phone number | Yes | No |
| Detect with | !isLidJid(jid) | isLidJid(jid) |
A LID (“linked identity”) is a stable, opaque identifier that represents a user without exposing their phone number. WhatsApp increasingly uses LIDs in groups and communities so members can interact without sharing their number.
import { isLidJid } from 'zapo-js'
isLidJid('5511999999999@s.whatsapp.net') // false (PN)
isLidJid('199998888777@lid') // true (LID)
Addressing mode
When sending into a group, the message is addressed to participants either by PN or by LID — the addressingMode: 'pn' | 'lid'. zapo resolves this automatically: it scans the group participants and uses lid if any participant is a LID, otherwise pn. The server can confirm or override the choice, which is reflected back in the publish result:
const result = await client.message.send(groupJid, 'hi')
console.log(result.ack.addressingMode) // 'pn' | 'lid'
You normally don’t set this yourself — it’s derived from the group’s membership.
What you get on an incoming message
A WaIncomingMessageEvent carries both identifiers when the server provides them:
| Field | Meaning |
|---|
senderJid | The sender’s primary JID for this message (PN or LID, matching the addressing mode). |
senderAlt | The alternate form — if senderJid is a PN, this is the LID, and vice versa (when available). |
recipientJid | Your receiving JID. |
recipientAlt | The alternate form of the recipient (when available). |
client.on('message', (event) => {
console.log('primary:', event.senderJid) // e.g. 1999...@lid in a LID group
console.log('alt: ', event.senderAlt) // e.g. 5511...@s.whatsapp.net
})
So in a LID-addressed group you’ll typically see senderJid as a @lid and senderAlt as the phone JID (if the server shares it).
Replying — which JID to use
client.message.send accepts either a PN or a LID JID and normalizes the target for you, so you rarely have to convert:
// All valid:
await client.message.send('5511999999999', 'hi') // digits → PN JID
await client.message.send('5511999999999@s.whatsapp.net', 'hi') // PN JID
await client.message.send('199998888777@lid', 'hi') // LID JID
Always prefer sending by LID when you have one. The LID is the privacy-preserving, forward-compatible identity WhatsApp is migrating to — addressing a peer by LID is the future-proof choice and avoids leaking/relying on phone numbers. Fall back to the PN only when no LID is available.Get the LID from the incoming event’s senderAlt (when senderJid is a PN) or resolve it with getLidsByPhoneNumbers.
In a group, always reply to event.chatJid (the group JID), not to a participant’s JID. For 1:1 chats, prefer the peer’s LID; event.chatJid / event.senderJid also work whether they are a PN or a LID.
Mapping a phone number to its LID
To resolve LIDs for a set of phone numbers, use the profile coordinator:
const results = await client.profile.getLidsByPhoneNumbers([
'5511999999999',
'5511888888888'
])
for (const r of results) {
// SignalLidSyncResult: { phoneJid, lidJid, exists }
console.log(r.phoneJid, '→', r.lidJid, '(exists:', r.exists, ')')
}
lidJid is null when the server has no LID mapping for that number.
LID changes
A user’s LID can change (server-side, for privacy). When it does, you receive a mex_notification of kind lid_change:
client.on('mex_notification', (event) => {
if (event.kind === 'lid_change') {
console.log('LID changed:', event.oldLidJid, '→', event.newLidJid)
// WaMexLidChangeEvent
}
})
zapo handles the underlying Signal-session bookkeeping; this event is for your own caches/bookkeeping.
Where PN ↔ LID linkage is stored
Several app-state schemas track the relationship and sync it across your devices (see chat mutations):
| Schema | Role |
|---|
LidContact | Contact profile (name/username) keyed by a LID. |
PnForLidChat | Remembers the phone JID for a chat that is primarily addressed by LID. |
ShareOwnPn | Whether your own phone number is shared in a given context. |
Signal sessions
Signal sessions are keyed by the canonical JID (PN or LID) plus device id. zapo canonicalizes hosted server variants (hosted.lid → lid, hosted → s.whatsapp.net) before lookups, and maintains sessions for both addressing forms. If you ever need to force a fresh session sync for a peer, use:
await client.message.syncSignalSession(jid)