Identidades: PN, LID y peer_ref
En WhatsApp, una misma persona puede aparecer con dos identificadores distintos, y no siempre
con su teléfono. Si tu app usa el número de teléfono como clave del contacto, tarde o temprano
va a ver un mensaje sin número, o el mismo contacto dos veces. El motor resuelve eso con el
peer_ref: una clave estable por instancia que agrupa todos los identificadores comprobados
de un chat.
Los identificadores de WhatsApp
Sección titulada «Los identificadores de WhatsApp»WhatsApp nombra cada chat con un JID: un texto usuario@servidor. El servidor dice qué es:
| JID | Qué es | clase en el motor |
|---|---|---|
CONTACTO@s.whatsapp.net |
PN (phone number): la cuenta por su número de teléfono. | usuario |
CONTACTO@lid |
LID (linked identity): un identificador opaco de la misma cuenta, que no revela el número. | usuario |
GRUPO@g.us |
Un grupo. | grupo |
CANAL@newsletter |
Un canal. | canal |
…@broadcast |
Una lista de difusión o los status. | difusion |
WhatsApp usa cada vez más el LID: en los grupos donde alguien oculta su número, en las comunidades y, en muchos teléfonos, también en los chats 1:1. Un LID no se puede convertir en teléfono. El único modo de saber que un LID y un PN son la misma persona es que el servidor de WhatsApp los informe juntos.
El peer_ref
Sección titulada «El peer_ref»Para cada instancia, el motor guarda un peer por cada chat o persona que conoce, con un
peer_ref (un UUID) y sus alias: los JID que se comprobaron como suyos.
- Es estable: si el contacto aparece hoy por su PN y mañana por su LID, sigue siendo el mismo
peer_ref, y el mismo mensaje que llega por los dos caminos no se duplica. - Es por instancia: el mismo contacto tiene un
peer_refdistinto en cada instancia, aunque sean de la misma app. No lo uses como clave global de la persona. - No es de WhatsApp: es del motor. No lo encontrarás en un teléfono ni en otra herramienta.
Cómo se juntan los alias
Sección titulada «Cómo se juntan los alias»Un alias solo se suma a un peer cuando el servidor informa los dos identificadores en el mismo hecho: el remitente de un mensaje con su identificador alternativo, el destinatario de un recibo, el mapa LID↔PN que el SDK guardó para esa instancia, o una consulta al servidor. Nunca por el nombre, ni por parecido, ni por lo que sabe otra instancia.
Si el motor ya tenía dos peers (uno por el PN y otro por el LID, vistos por separado) y después
comprueba que son la misma persona, los une en el más antiguo: los mensajes, el estado de los
chats, las etiquetas y el contacto pasan a ese peer. El peer_ref del más nuevo deja de existir, y
el ledger lo avisa con un peer.merged: el peer que queda, con
sus alias, y los peer_ref unidos.
El peer en los eventos
Sección titulada «El peer en los eventos»Los chats, los autores, los grupos y los canales viajan en los eventos como un peer:
{ "peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": ["CONTACTO@s.whatsapp.net"]}| Campo | Qué es |
|---|---|
peer_ref |
La clave estable en esta instancia. Úsala como clave del chat en tu sistema. |
jid |
El JID con el que llegó este hecho. Puede ser el LID en un evento y el PN en el siguiente. |
clase |
usuario, grupo, difusion, canal u otro. |
aliases |
Los demás JID comprobados del peer. Vacío si solo se conoce uno. |
En un grupo, el mensaje trae el grupo en chat y la persona que escribió en autor, cada uno con
su peer. Un autor que oculta su número llega con un jid @lid y aliases: [].
Responder
Sección titulada «Responder»Para contestar un mensaje, usa el peer_ref de su chat:
{ "destino": { "peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1" }, "texto": "¡Gracias por escribirnos!" }| Destino | Cuándo | Cuidado |
|---|---|---|
peer_ref |
Responder a alguien que te escribió, o a un chat que tu app ya guardó. | Solo vale en su instancia (otra da 422). |
jid |
Un chat que conoces por su JID: un grupo, un LID, un PN. | Va tal cual. |
telefono |
Escribirle por primera vez a un número. | El motor arma el PN con el número. No te dice si es la misma persona que ya conoces por un LID: eso lo dice el peer_ref del chat. |
Con peer_ref, el motor envía al JID comprobado más antiguo del peer. Las menciones aceptan
las mismas tres formas.
Resolver una identidad
Sección titulada «Resolver una identidad»Hay dos caminos, y no son equivalentes:
GET …/identities |
POST …/contacts/check |
|
|---|---|---|
| Scope | contacts:read |
contacts:lookup |
| Pregunta a | Lo que esta instancia ya comprobó | El servidor de WhatsApp |
| Cuota | No consume | Cada número cuenta en la cuota diaria de la app (500 de fábrica) |
| Si no sabe | 404 identidad_desconocida |
Responde registrado: false |
| Crea contactos o peers | No | No |
GET /v1/instances/{id}/identities recibe uno de telefono, jid o peer_ref y devuelve el
peer con su PN, su LID y su teléfono solo si esta instancia los comprobó:
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/identities?jid=CONTACTO@lid" \ -H "Authorization: Bearer TU_API_KEY"// El SDK no trae identities: es una petición HTTP con el mismo token.q := url.Values{"jid": {"CONTACTO@lid"}}req, _ := http.NewRequestWithContext(ctx, http.MethodGet, c.Base+"/v1/instances/"+inst.ID+"/identities?"+q.Encode(), nil)req.Header.Set("Authorization", "Bearer "+c.Token)res, err := c.HTTP.Do(req) // 200, o 404 identidad_desconocidaconst q = new URLSearchParams({ jid: 'CONTACTO@lid' });const res = await fetch( `https://motor.ejemplo.com/v1/instances/${instanciaId}/identities?${q}`, { headers: { Authorization: `Bearer ${apiKey}` } },);const identidad = res.status === 404 ? null : await res.json();{ "peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": ["CONTACTO@s.whatsapp.net"], "pn": "CONTACTO@s.whatsapp.net", "lid": "CONTACTO@lid", "telefono": "+NUMERO_DEL_CONTACTO", "contacto": true}telefono solo aparece si hay un PN comprobado: un LID solo nunca da un teléfono. contacto
dice si la persona está entre los contactos del número (los de GET …/contacts).
POST /v1/instances/{id}/contacts/check pregunta al servidor si unos números (hasta 20 por
consulta) tienen WhatsApp, y trae el JID canónico (que puede ser un LID), el PN y el LID. Si la
instancia ya conocía al contacto, el par que dio el servidor queda comprobado en su peer y la
respuesta trae su peer_ref. No es un oráculo público: consume cuota, y un número inválido o la
cuota agotada rechazan la consulta entera antes de llegar al servidor. Los detalles están en
Contactos.
Lo que se vio con números reales
Sección titulada «Lo que se vio con números reales»- Las menciones que llegan van por LID. En un grupo, el
message.receivedde una mención al número de la instancia trajo encontenido.mencionesun JID@lid(prueba del 21/10/2026). No hay un campo «me mencionaron»: compara la lista con los JID propios de la instancia, su PN y su LID. El motor guarda el LID propio como alias del propio número al conectar, así queidentities?telefono=con elnumerode la instancia devuelve los dos. - El teléfono guarda los chats 1:1 por LID. De los cambios de chats que llegaron del teléfono
en una instancia, 60 chats 1:1 vinieron por
@lidy uno solo por@s.whatsapp.net(falla F12, 16/10/2026). El motor ahora manda los cambios de estado de un chat (fijar, archivar, silenciar, destacar) por el LID cuando lo conoce.
La privacidad de los identificadores
Sección titulada «La privacidad de los identificadores»Un teléfono es un dato personal, y un LID existe justamente para no revelarlo. El motor los trata así:
- Nunca en claro en la base: cada JID se guarda como un HMAC (para buscarlo) y sellado (para leerlo). Tampoco en los logs ni en la auditoría: una consulta de números registra cuántos, nunca cuáles.
- Nada se comparte entre instancias: el mapa LID↔PN del SDK es de todo el proceso, pero el motor solo expone a cada instancia los pares que aprendió ella. La instancia de otra app no recibe el teléfono detrás de un LID que solo vio la tuya.
- Nada se infiere: ni un teléfono a partir de un LID, ni una persona por su nombre.
- Las consultas al servidor tienen cuota por app, para que el motor no sirva para barrer números.
Lo que sí sale en claro son tus eventos y respuestas: jid, aliases, telefono. Desde ese
momento, protegerlos es tarea de tu app.
En resumen
Sección titulada «En resumen»- Usa el
peer_ref(junto con elinstance_id) como clave de cada chat en tu sistema. - Guarda también el
jidy losaliasesdel último evento, para volver a resolverlo si hace falta. - Responde con
destino.peer_ref; usatelefonosolo para escribir por primera vez. - Trata a un contacto sin
telefonocomo normal: muchos solo tendrán su LID. - Nunca uses el teléfono como identidad global, ni intentes deducirlo de un LID.