Eventos
Para notificar a tu aplicación sobre eventos de WhatsApp, puedes usar Webhooks y WebSockets.
Webhooks
Los webhooks permiten que dos aplicaciones se comuniquen en tiempo real: cuando ocurre un evento, Wazend envía una petición HTTP POST a tu URL.
Webhooks por sesión
Configura los webhooks por sesión en la configuración de la sesión:
{
"name": "S0001",
"config": {
"webhooks": [
{
"url": "https://tu-servidor.com/webhook",
"events": ["message"],
"hmac": { "key": "tu-clave-secreta" },
"retries": {
"policy": "constant",
"delaySeconds": 2,
"attempts": 15
},
"customHeaders": [
{ "name": "X-Mi-Header", "value": "Valor" }
]
}
]
}
}Reintentos
Políticas de reintento disponibles:
constant— mismo retraso entre intentos (2, 2, 2).linear— retroceso lineal (2, 4, 6, 8).exponential— retroceso exponencial con jitter.
Headers
Cada webhook incluye estos headers:
X-Webhook-Request-Id— ID único de la petición.X-Webhook-Timestamp— timestamp Unix en ms.X-Webhook-Hmac— código de autenticación del cuerpo (si usas HMAC).X-Webhook-Hmac-Algorithm— algoritmo usado (sha512).
Autenticación HMAC
Define tu clave secreta en config.hmac.key y verifica la firma en el header X-Webhook-Hmac (algoritmo sha512) contra el cuerpo de la petición.
{
"name": "S0001",
"config": {
"webhooks": [
{
"url": "https://tu-servidor.com/webhook",
"events": ["message"],
"hmac": { "key": "tu-clave-secreta" }
}
]
}
}Ejemplos
Puedes automatizar flujos con n8n.
WebSockets
Usa WebSockets para recibir mensajes en tiempo real.
# Escuchar todas las sesiones y eventos
websocat -E ws://tu-servidor.wazend.net/ws
# Conexión segura (wss://)
websocat -E wss://tu-servidor.wazend.net/ws
# Con API key
websocat -E ws://tu-servidor.wazend.net/ws?x-api-key=123
# Escuchar ciertos eventos
websocat -E "ws://tu-servidor.wazend.net/ws?session=*&events=session.status&events=message"
# Escuchar una sesión concreta
websocat -E "ws://tu-servidor.wazend.net/ws?session=S0001&events=session.status"Parámetros:
session— nombre de la sesión o*para todas.events— lista de eventos o*.x-api-key— tu API key.
Estructura del payload
{
"id": "evt_1111111111111111111111111111",
"timestamp": 1741249702485,
"event": "message",
"session": "S0001",
"metadata": {
"user.id": "123",
"user.email": "cliente@ejemplo.com"
},
"me": {
"id": "5215551234567@c.us",
"pushName": "Mi Nombre"
},
"payload": {
"...": "datos específicos del evento"
},
"environment": {
"tier": "CORE",
"version": "2023.10.12"
},
"engine": "NOWEB"
}Metadatos
Puedes enviar metadata personalizada al crear o actualizar la sesión; se incluirá tal cual en el campo metadata de cada evento de webhook. Esto te permite enrutar eventos al usuario correcto sin consultas adicionales a tu base de datos.
Eventos disponibles
Resumen de todos los eventos disponibles. Haz clic en cada evento para ir a su detalle:
| Evento | Para qué sirve |
|---|---|
| session.status | Cambio de estado de la sesión (STOPPED, STARTING, SCAN_QR_CODE, WORKING, FAILED, PASSKEY). |
| engine.event | Evento de bajo nivel del motor para depuración (no se incluye en events=*). |
| message | Mensaje entrante (texto, audio, archivos). |
| message.any | Todos los mensajes, incluidos los tuyos. |
| message.reaction | Reacción a un mensaje. |
| message.ack | Confirmación de entrega, lectura o reproducción del mensaje. |
| message.ack.group | Confirmación de un participante en un grupo. |
| message.waiting | Mensaje en espera («Esperando este mensaje...»). |
| message.edited | Mensaje editado. |
| message.revoked | Mensaje eliminado (revocado). |
| chat.archive | Chat archivado o desarchivado. |
| group.v2.join | Te unes o te añaden a un grupo. |
| group.v2.leave | Sales o te quitan de un grupo. |
| group.v2.participants | Alguien entra, sale, es promovido o degradado. |
| group.v2.update | Se actualiza la información del grupo. |
| presence.update | Actualización de presencia (online, offline, escribiendo, grabando). |
| poll.vote | Nuevos votos de encuesta. |
| poll.vote.failed | Voto de encuesta no descifrable. |
| event.response | Respuesta a un mensaje de evento (GOING, NOT_GOING, MAYBE). |
| event.response.failed | Respuesta de evento no descifrable. |
| label.upsert | Etiqueta creada o actualizada. |
| label.deleted | Etiqueta eliminada. |
| label.chat.added | Etiqueta añadida a un chat. |
| label.chat.deleted | Etiqueta quitada de un chat. |
| call.received | Llamada entrante. |
| call.accepted | Llamada aceptada (en otro dispositivo). |
| call.rejected | Llamada rechazada o terminada. |
session.status
Se dispara cuando cambia el estado de la sesión:
STOPPED— la sesión está detenida.STARTING— la sesión está iniciando.SCAN_QR_CODE— se requiere escanear el QR o iniciar por teléfono. Se emite cada vez que el QR cambia; al recibirlo, obtén el QR actualizado.PASSKEY_REQUIRED— WhatsApp pide una passkey (WebAuthn);datacontiene el reto a firmar.PASSKEY_CONFIRMATION_REQUIRED— el usuario debe verificar un código;datacontiene el código.WORKING— la sesión funciona y está lista.FAILED— la sesión falló; intenta reiniciar, y si no, cierra sesión y vuelve a iniciarla.
{
"event": "session.status",
"session": "S0001",
"payload": {
"status": "WORKING",
"statuses": [
{ "status": "STOPPED", "timestamp": 1700000001000 },
{ "status": "STARTING", "timestamp": 1700000002000 },
{ "status": "WORKING", "timestamp": 1700000003000 }
],
"data": null
}
}status es el estado actual; statuses los últimos 3 estados; data lleva info extra del estado.
Ejemplo con PASSKEY_REQUIRED:
{
"event": "session.status",
"session": "S0001",
"payload": {
"status": "PASSKEY_REQUIRED",
"statuses": [],
"data": {
"challenge": "9WVUYm9AsQ...",
"timeout": 60000,
"rpId": "web.whatsapp.com",
"allowCredentials": [
{ "id": "AX8bTgH2...", "type": "public-key", "transports": ["internal", "hybrid"] }
],
"userVerification": "required"
}
}
}Ejemplo con PASSKEY_CONFIRMATION_REQUIRED:
{
"event": "session.status",
"session": "S0001",
"payload": {
"status": "PASSKEY_CONFIRMATION_REQUIRED",
"statuses": [],
"data": { "code": "1234" }
}
}Ejemplo con Reachout Timelock (restricción de envío a contactos nuevos, causa del error 463):
{
"event": "session.status",
"session": "S0001",
"payload": {
"status": "WORKING",
"statuses": [],
"data": {
"reachoutTimelock": {
"enforcementType": "RESTRICT_ALL_COMPANIONS",
"isActive": true,
"timeEnforcementEnds": 1784477333
}
}
}
}timeEnforcementEnds. Lee más en Cómo evitar bloqueo.Ejemplo con Message Capping (cuota por ciclo de contactos nuevos, causa del error 475):
{
"event": "session.status",
"session": "S0001",
"payload": {
"status": "WORKING",
"statuses": [],
"data": {
"messageCapping": {
"cappingStatus": "FIRST_WARNING",
"totalQuota": 1000,
"usedQuota": 640,
"cycleStart": 1782874800,
"cycleEnd": 1785553199,
"mvStatus": "NOT_ELIGIBLE",
"oteStatus": "NOT_ELIGIBLE"
}
}
}
}cappingStatus CAPPED, pausa el envío a contactos nuevos hasta cycleEnd. Lee más en Cómo evitar bloqueo.message
Mensaje entrante (texto/audio/archivos).
{
"event": "message",
"session": "S0001",
"payload": {
"id": "true_5215551234567@c.us_AAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "5215551234567@c.us",
"fromMe": true,
"source": "app",
"to": "5215551234567@c.us",
"body": "¡Hola!",
"hasMedia": false,
"ack": 1
}
}hasMedia— indica si el mensaje tiene multimedia.media.url— URL para descargar el multimedia.source—appoapi(cuando envías vía API)._data— datos internos del motor.
message.any
Se dispara en todos los mensajes, incluidos los tuyos. Mismo payload que message.
{
"event": "message.any",
"session": "S0001",
"payload": {
"id": "true_5215551234567@c.us_AAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "5215551234567@c.us",
"fromMe": true,
"source": "api",
"to": "5215551234567@c.us",
"body": "¡Hola!",
"hasMedia": false,
"ack": 1
}
}message.reaction
Reacción a un mensaje. reaction.text es el emoji (vacío si se quitó) y reaction.messageId el ID del mensaje reaccionado.
{
"event": "message.reaction",
"session": "S0001",
"payload": {
"id": "false_5215551234567@c.us_11111111111111111111111111111111",
"from": "5215551234567@c.us",
"fromMe": false,
"reaction": {
"text": "🙏",
"messageId": "true_5215551234567@c.us_11111111111111111111111111111111"
}
}
}message.ack
Confirmación de entrega/lectura del mensaje:
ERROR, ack: -1— ocurrió un error.PENDING, ack: 0— mensaje pendiente.SERVER, ack: 1— enviado al servidor.DEVICE, ack: 2— enviado al dispositivo.READ, ack: 3— leído.PLAYED, ack: 4— reproducido.
{
"event": "message.ack",
"session": "S0001",
"payload": {
"id": "true_5215551234567@c.us_4CC5EDD64BC22EBA6D639F2AF571346C",
"from": "5215551234567@c.us",
"fromMe": true,
"ack": 3,
"ackName": "READ"
}
}message.ack.group
Confirmación de un participante en un grupo (incluye el campo participant).
{
"event": "message.ack",
"session": "S0001",
"payload": {
"id": "true_5215551234567@g.us_4CC5EDD64BC22EBA6D639F2AF571346C_9999@lid",
"from": "5215551234567@g.us",
"participant": "9999@lid",
"fromMe": true,
"ack": 3,
"ackName": "READ"
}
}message.waiting
Ocurre cuando aparece "Esperando este mensaje. Puede tardar un poco" en el teléfono.
{
"event": "message.waiting",
"session": "S0001",
"payload": {
"id": "true_5215551234567@c.us_AAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "5215551234567@c.us",
"fromMe": true,
"to": "5215551234567@c.us",
"_data": {}
}
}message.edited
Mensaje editado. editedMessageId es el ID del mensaje editado y body el nuevo texto. El id del evento tiene el formato false_{chatId}_{messageId}[_{participant}].
{
"event": "message.edited",
"session": "S0001",
"payload": {
"id": "false_5215551234567@c.us_AAAAAAAAAAAAAAAA[_5215551234567@c.us]",
"editedMessageId": "AAAAAAAAAAAAAAAA",
"body": "Nuevo texto",
"_data": {}
}
}message.revoked
Mensaje eliminado (revocado). revokedMessageId es el ID del mensaje eliminado y after.id el del evento de revocación (formato false_{chatId}_{messageId}[_{participant}]).
{
"event": "message.revoked",
"session": "S0001",
"payload": {
"after": {
"id": "false_5215551234567@c.us_AAAAAAAAAAAAAAAA",
"_data": {}
},
"revokedMessageId": "AAAAAAAAAAAAAAAA",
"before": null
}
}chat.archive
Chat archivado o desarchivado.
{
"event": "chat.archive",
"session": "S0001",
"payload": {
"id": "5215551234567@c.us",
"timestamp": 1667561485,
"archived": true
}
}group.v2.join
Te unes o te añaden a un grupo.
{
"event": "group.v2.join",
"session": "S0001",
"payload": {
"group": {
"id": "5215551234567@g.us",
"subject": "Grupo de trabajo",
"description": "Descripción del grupo",
"invite": "https://chat.whatsapp.com/invitecode",
"membersCanAddNewMember": true,
"membersCanSendMessages": true,
"newMembersApprovalRequired": true,
"participants": [
{ "id": "5219999999999@c.us", "role": "participant" }
]
},
"timestamp": 789456123
}
}group.v2.leave
Sales o te quitan de un grupo.
{
"event": "group.v2.leave",
"session": "S0001",
"payload": {
"group": { "id": "5215551234567@g.us" },
"timestamp": 789456123
}
}group.v2.participants
Alguien entra, sale, es promovido o degradado. type puede ser join, leave, promote o demote.
{
"event": "group.v2.participants",
"session": "S0001",
"payload": {
"type": "join",
"timestamp": 1666943582,
"group": { "id": "5215551234567@g.us" },
"participants": [
{ "id": "5219999999999@c.us", "role": "participant" }
],
"_data": {}
}
}Roles posibles: left, participant, admin y superadmin.
group.v2.update
Se actualiza la información del grupo.
{
"event": "group.v2.update",
"session": "S0001",
"payload": {
"group": { "id": "5215551234567@g.us", "subject": "Nuevo nombre del grupo" },
"timestamp": 789456123,
"_data": {}
}
}presence.update
Actualización de presencia. lastKnownPresence puede ser online, offline, typing, recording o paused. El campo payload.id indica el chat (directo o grupo) y participant el participante concreto (en chats directos solo hay uno).
{
"event": "presence.update",
"session": "S0001",
"payload": {
"id": "5215551234567@c.us",
"presences": [
{
"participant": "5215551234567@c.us",
"lastKnownPresence": "typing",
"lastSeen": null
}
]
}
}poll.vote
Recibes nuevos votos de encuestas. Identifica los tuyos con poll.fromMe.
{
"event": "poll.vote",
"session": "S0001",
"payload": {
"vote": {
"id": "false_5215551234567@c.us_83ACBE602A05C79B234B54415E95EE8A",
"to": "me",
"from": "5215551234567@c.us",
"fromMe": false,
"selectedOptions": ["¡Genial!"],
"timestamp": 1692861427
},
"poll": {
"id": "true_5215551234567@c.us_BAE5F2EF5C69001E",
"to": "5215551234567@c.us",
"from": "me",
"fromMe": true
}
}
}poll.vote.failed
Igual que poll.vote pero con selectedOptions vacío (no se pudo descifrar el voto).
{
"event": "poll.vote.failed",
"session": "S0001",
"payload": {
"vote": {
"id": "false_5215551234567@c.us_2E8C4CDA89EDE3BC0BC7F605364B8451",
"to": "me",
"from": "5215551234567@c.us",
"fromMe": false,
"selectedOptions": [],
"timestamp": 1692956972
},
"poll": {
"id": "true_5215551234567@c.us_BAE595F4E0A2042C",
"to": "5215551234567@c.us",
"from": "me",
"fromMe": true
}
}
}event.response
Respuesta a un mensaje de evento. eventResponse.response puede ser GOING, NOT_GOING o MAYBE. Lee más en Mensajes de evento.
{
"id": "evt_00000000000000000000001",
"session": "S0001",
"event": "event.response",
"payload": {
"id": "false_5215551234567@c.us_58BBBBBBBBBBBBBBBBBBBBBBBB",
"timestamp": 1747707858,
"from": "5215551234567@c.us",
"participant": null,
"fromMe": false,
"eventCreationKey": {
"id": "false_5215551234567@c.us_3EBAAAAAAAAAAAAAAAAAAAAAAAAAA",
"to": "me",
"from": "5215551234567@c.us",
"fromMe": false
},
"eventResponse": {
"response": "GOING",
"timestampMs": 1747707858429,
"extraGuestCount": 0
},
"source": "app",
"ack": null,
"ackName": "UNKNOWN",
"replyTo": null,
"_data": {}
}
}eventCreationKey— información del mensaje de evento original (id,to,from,fromMe).eventResponse.response—GOING,NOT_GOINGoMAYBE.eventResponse.timestampMs— timestamp Unix en ms de la respuesta.eventResponse.extraGuestCount— invitados adicionales (0 o 1).replyTo— info del mensaje al que se responde (nullsi no es una respuesta).
event.response.failed
No se pudo descifrar la respuesta (con eventResponse: null). Reenvía el mensaje de evento para obtener una nueva respuesta. Lee más en Mensajes de evento.
{
"id": "evt_00000000000000000000001",
"session": "S0001",
"event": "event.response.failed",
"payload": {
"id": "false_5215551234567@c.us_58BBBBBBBBBBBBBBBBBBBBBBBB",
"timestamp": 1747707858,
"from": "5215551234567@c.us",
"participant": null,
"fromMe": false,
"eventCreationKey": {
"id": "false_5215551234567@c.us_3EBAAAAAAAAAAAAAAAAAAAAAAAAAA",
"to": "me",
"from": "5215551234567@c.us",
"fromMe": false
},
"eventResponse": null,
"source": "app",
"ack": null,
"ackName": "UNKNOWN",
"replyTo": null,
"_data": {}
}
}label.upsert
Etiqueta creada o actualizada.
{
"event": "label.upsert",
"session": "S0001",
"payload": {
"id": "10",
"name": "Cliente VIP",
"color": 14,
"colorHex": "#00a0f2"
}
}label.deleted
Etiqueta eliminada.
{
"event": "label.deleted",
"session": "S0001",
"payload": {
"id": "10",
"name": "",
"color": 14,
"colorHex": "#00a0f2"
}
}label.chat.added
Etiqueta añadida a un chat (labelId + chatId). Justo tras escanear el QR, label puede ser null.
{
"event": "label.chat.added",
"session": "S0001",
"payload": {
"labelId": "6",
"chatId": "5215551234567@c.us",
"label": null
}
}label.chat.deleted
Etiqueta quitada de un chat (labelId + chatId).
{
"event": "label.chat.deleted",
"session": "S0001",
"payload": {
"labelId": "6",
"chatId": "5215551234567@c.us",
"label": null
}
}call.received
Llamada entrante.
{
"event": "call.received",
"session": "S0001",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "5215551234567@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false
}
}call.accepted
Llamada aceptada (en otro dispositivo).
{
"event": "call.accepted",
"session": "S0001",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "5215551234567@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false,
"_data": {}
}
}call.rejected
Llamada rechazada o terminada.
{
"event": "call.rejected",
"session": "S0001",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "5215551234567@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false,
"_data": {}
}
}En el motor WEBJS, call.rejected solo se dispara cuando rechazas la llamada mediante la API.
engine.event
Evento de bajo nivel del motor, para debug y diagnóstico. No se incluye al suscribirse con events=*; debes especificarlo explícitamente.
{
"event": "engine.event",
"session": "S0001",
"engine": "NOWEB",
"payload": {
"event": "messages.upsert",
"data": {}
}
}