wsperu.devDocs

Webhooks

Recibe eventos de mensajes y sesiones en tu sistema, con firma HMAC.

Registra una URL tuya y te enviaremos un POST con cada evento que te interese.

Administrar webhooks

GET    /webhooks           # listar
POST   /webhooks           # crear
PUT    /webhooks/{id}      # actualizar (url, events, active)
DELETE /webhooks/{id}      # eliminar
POST /webhooks
{
  "url": "https://tu-sistema.com/webhooks/wsperu",
  "events": ["message.status", "message.received", "session.disconnected"],
  "sessionId": "665f..."
}

Al crear, la respuesta incluye un secret — guárdalo para verificar la firma.

Alcance por sesión (opcional)

sessionId limita el webhook a una línea:

  • Omitido / null → el webhook se dispara para todas las sesiones de la cuenta.
  • Con sessionId → solo se dispara para esa línea.

Así podés tener un webhook global y/o webhooks separados por línea (ej: cobranza → sistema A, ventas → sistema B).

Eventos

Evento Cuándo se dispara
message.sent El mensaje salió hacia WhatsApp
message.status Cambió el estado de entrega (delivered, read)
message.failed El envío falló tras los reintentos
message.received Tu línea recibió un mensaje entrante
session.connected La línea se conectó
session.disconnected La línea se desconectó
session.qr Se generó un QR nuevo (útil para re-vincular)

Todos los eventos incluyen sessionId y sessionName al nivel raíz, así siempre identificás de qué línea proviene el evento (por id y por el nombre que le pusiste).

Ejemplo de payload

{
  "event": "message.received",
  "sessionId": "665f...",
  "sessionName": "COBRANZA",
  "data": {
    "from": "51987654321@s.whatsapp.net",
    "number": "51987654321",
    "pushName": "Juan Pérez",
    "messageId": "3EB0...",
    "timestamp": 1755011111,
    "type": "conversation",
    "text": "Hola, quisiera información sobre mi pedido"
  }
}

Campos de data en message.received:

Campo Descripción
from JID del chat (puede ser @lid si el remitente oculta su número)
number Número real del remitente (resuelto aunque el from sea @lid)
pushName Nombre público que el remitente tiene en WhatsApp
messageId Id del mensaje en WhatsApp
timestamp Época Unix (segundos)
type Tipo de mensaje (ver tabla abajo)
text Cuerpo del mensaje, o el caption si es imagen/video/documento; null para media sin caption. En una reacción, es el emoji
media Presente solo si el mensaje trae un archivo (ver siguiente sección)

Valores de type (mensajes entrantes)

conversation (texto) · extendedTextMessage (texto con formato/link) · imageMessage · videoMessage · audioMessage · documentMessage · stickerMessage · reactionMessage · locationMessage · liveLocationMessage · contactMessage · contactsArrayMessage · pollCreationMessage · buttonsResponseMessage · listResponseMessage. Cualquier otro tipo llega como unknown.

Media entrante (imágenes, audio, video, documentos)

Cuando tu línea recibe un archivo, lo descargamos de WhatsApp, lo guardamos y adjuntamos un objeto media en data con una URL de descarga:

{
  "event": "message.received",
  "sessionId": "665f...",
  "sessionName": "COBRANZA",
  "data": {
    "from": "51987654321@s.whatsapp.net",
    "number": "51987654321",
    "pushName": "Juan Pérez",
    "messageId": "3EB0...",
    "timestamp": 1755011111,
    "type": "imageMessage",
    "text": "el caption, si lo hubo",
    "media": {
      "url": "https://.../received/.../3EB0..._foto.jpg?...",
      "mimetype": "image/jpeg",
      "size": 216612,
      "filename": "foto.jpg"
    }
  }
}
Campo de media Descripción
url URL de descarga directa del archivo (válida por 24 horas)
mimetype Tipo de contenido (ej: image/jpeg, audio/ogg, application/pdf)
size Tamaño en bytes
filename Nombre del archivo (el original en documentos; generado en el resto)

Descargá el archivo apenas recibas el webhook y guardalo de tu lado — la url expira a las 24 horas. Se cubren imagen, video, audio, sticker y documento.

Cada request incluye el header x-webhook-signature: el HMAC-SHA256 del body usando tu secret.

const crypto = require('crypto');

function esValido(body, firma, secret) {
  const esperada = crypto.createHmac('sha256', secret)
    .update(JSON.stringify(body))
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
}

Reintentos

Si tu endpoint no responde 2xx, reintentamos hasta 3 veces con backoff exponencial. Responde rápido (encola internamente si tu procesamiento es lento) — el timeout es de 10 segundos.