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:

jsonBody
{
  "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.

jsonBody
{
  "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.

bash
# 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

jsonEvento
{
  "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:

EventoPara qué sirve
session.statusCambio de estado de la sesión (STOPPED, STARTING, SCAN_QR_CODE, WORKING, FAILED, PASSKEY).
engine.eventEvento de bajo nivel del motor para depuración (no se incluye en events=*).
messageMensaje entrante (texto, audio, archivos).
message.anyTodos los mensajes, incluidos los tuyos.
message.reactionReacción a un mensaje.
message.ackConfirmación de entrega, lectura o reproducción del mensaje.
message.ack.groupConfirmación de un participante en un grupo.
message.waitingMensaje en espera («Esperando este mensaje...»).
message.editedMensaje editado.
message.revokedMensaje eliminado (revocado).
chat.archiveChat archivado o desarchivado.
group.v2.joinTe unes o te añaden a un grupo.
group.v2.leaveSales o te quitan de un grupo.
group.v2.participantsAlguien entra, sale, es promovido o degradado.
group.v2.updateSe actualiza la información del grupo.
presence.updateActualización de presencia (online, offline, escribiendo, grabando).
poll.voteNuevos votos de encuesta.
poll.vote.failedVoto de encuesta no descifrable.
event.responseRespuesta a un mensaje de evento (GOING, NOT_GOING, MAYBE).
event.response.failedRespuesta de evento no descifrable.
label.upsertEtiqueta creada o actualizada.
label.deletedEtiqueta eliminada.
label.chat.addedEtiqueta añadida a un chat.
label.chat.deletedEtiqueta quitada de un chat.
call.receivedLlamada entrante.
call.acceptedLlamada aceptada (en otro dispositivo).
call.rejectedLlamada 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); data contiene el reto a firmar.
  • PASSKEY_CONFIRMATION_REQUIRED — el usuario debe verificar un código; data contiene 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.
jsonEvento
{
  "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:

jsonEvento
{
  "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:

jsonEvento
{
  "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):

jsonEvento
{
  "event": "session.status",
  "session": "S0001",
  "payload": {
    "status": "WORKING",
    "statuses": [],
    "data": {
      "reachoutTimelock": {
        "enforcementType": "RESTRICT_ALL_COMPANIONS",
        "isActive": true,
        "timeEnforcementEnds": 1784477333
      }
    }
  }
}

Ejemplo con Message Capping (cuota por ciclo de contactos nuevos, causa del error 475):

jsonEvento
{
  "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"
      }
    }
  }
}

message

Mensaje entrante (texto/audio/archivos).

jsonEvento
{
  "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.
  • sourceapp o api (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.

jsonEvento
{
  "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.

jsonEvento
{
  "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.
jsonEvento
{
  "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).

jsonEvento
{
  "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.

jsonEvento
{
  "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}].

jsonEvento
{
  "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}]).

jsonEvento
{
  "event": "message.revoked",
  "session": "S0001",
  "payload": {
    "after": {
      "id": "false_5215551234567@c.us_AAAAAAAAAAAAAAAA",
      "_data": {}
    },
    "revokedMessageId": "AAAAAAAAAAAAAAAA",
    "before": null
  }
}

chat.archive

Chat archivado o desarchivado.

jsonEvento
{
  "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.

jsonEvento
{
  "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.

jsonEvento
{
  "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.

jsonEvento
{
  "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.

jsonEvento
{
  "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).

jsonEvento
{
  "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.

jsonEvento
{
  "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).

jsonEvento
{
  "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.

jsonEvento
{
  "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_GOING o MAYBE.
  • eventResponse.timestampMs — timestamp Unix en ms de la respuesta.
  • eventResponse.extraGuestCount — invitados adicionales (0 o 1).
  • replyTo — info del mensaje al que se responde (null si 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.

jsonEvento
{
  "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.

jsonEvento
{
  "event": "label.upsert",
  "session": "S0001",
  "payload": {
    "id": "10",
    "name": "Cliente VIP",
    "color": 14,
    "colorHex": "#00a0f2"
  }
}

label.deleted

Etiqueta eliminada.

jsonEvento
{
  "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.

jsonEvento
{
  "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).

jsonEvento
{
  "event": "label.chat.deleted",
  "session": "S0001",
  "payload": {
    "labelId": "6",
    "chatId": "5215551234567@c.us",
    "label": null
  }
}

call.received

Llamada entrante.

jsonEvento
{
  "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).

jsonEvento
{
  "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.

jsonEvento
{
  "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.

jsonEvento
{
  "event": "engine.event",
  "session": "S0001",
  "engine": "NOWEB",
  "payload": {
    "event": "messages.upsert",
    "data": {}
  }
}