Webhooks — Eventos de mensaje

receive_message, send_message, send_bot_message, status_message y los eventos de WhatsApp Flows.

Es la familia que cubre la conversación en sí: lo que entra, lo que sale y qué pasó con
cada envío.

EventoCuándo se dispara
receive_messageEntra un mensaje de un contacto
send_messageSale un mensaje desde la plataforma, la app o la API
send_bot_messageUn chatbot responde. Payload anidado, ver abajo
status_messageCambia el estado de un mensaje que usted envió
wa_flow_sentSe envía un WhatsApp Flow
wa_flow_response_receivedEl contacto responde un Flow

Los seis se filtran por línea: solo recibe los de los números que marcó al configurar el
webhook.

Campos garantizados

Estos campos están siempre, con null cuando no hay valor:

CampoTipoNotas
idintId del mensaje en Wasapi
user_idintCuenta dueña
sender_idint | nullAgente que lo envió. null en los entrantes y en los que envía un bot, una campaña o la API
from_idintLa línea de WhatsApp por la que entró o salió
messagestring | nullEl texto
typestringin (entrante), out (saliente), system (nota interna del sistema)
message_typestringVer la tabla de tipos
wa_idstring⚠️ La clave de conversación: teléfono o BSUID. Ver Identidad del contacto
wam_idstring | nullId del mensaje en Meta. Es la mejor llave para descartar duplicados
context_wam_idstring | nullwam_id del mensaje citado, si es una respuesta
statusstringsent, delivered, read, played, failed, n/a
captionstring | nullTexto que acompaña a un archivo
filenamestring | nullNombre del archivo adjunto
datastring⚠️ Campo interno. No lo parsee — ver la advertencia abajo
reactionsstring | nullReacciones al mensaje
originstring | nullPor dónde se originó: web, app, api, chatbot, campaign, webhook, wa_business, survey, make, n8n, system
created_atstring
updated_atstring
deleted_atstring | null
bsuidstring | nullVer Identidad del contacto
wa_usernamestring | null
versionint1
⚠️

data es un campo interno de Wasapi, no JSON.

Viene vacío en la mayoría de los mensajes y, cuando trae algo, su formato no forma parte
del contrato y puede cambiar sin aviso. No dependa de él. Para adjuntos use filename
y caption.

Valores de message_type

ValorQué es
textTexto
image, audio, video, document, stickerMultimedia
templatePlantilla aprobada por Meta
interactiveMensaje con botones o lista
buttonRespuesta del contacto a un botón
cta_urlMensaje con botón de enlace
flow_responseRespuesta de un WhatsApp Flow
systemNota interna (asignación de agente, cierre de conversación…)
unsupportedTipo que WhatsApp no soporta reenviar

Pueden aparecer valores nuevos: Meta agrega tipos. Trate un valor desconocido como genérico
en vez de rechazar el evento.

Campos extra, según el camino

Además del piso garantizado, cada evento puede traer campos propios. No están declarados y
pueden faltar
: trátelos como opcionales.

Campo extraAparece en
error_code, error_messagestatus_message cuando status es failed
context_message, context_typeRespuestas a un mensaje citado

Ejemplo — receive_message

{
  "event": "receive_message",
  "data": {
    "id": 900001,
    "user_id": 1234,
    "sender_id": null,
    "from_id": 42,
    "message": "Hola, quiero información de precios",
    "type": "in",
    "message_type": "text",
    "wa_id": "573001234567",
    "wam_id": "wamid.HBgMNTczMDAxMjM0NTY3FQIAEhgU…",
    "context_wam_id": null,
    "status": "n/a",
    "caption": null,
    "filename": null,
    "data": "",
    "reactions": null,
    "origin": "webhook",
    "created_at": "2026-08-07T15:04:11.000000Z",
    "updated_at": "2026-08-07T15:04:11.000000Z",
    "deleted_at": null,
    "bsuid": null,
    "wa_username": null,
    "version": 1
  }
}

Ejemplo — status_message con fallo

{
  "event": "status_message",
  "data": {
    "id": 900002,
    "user_id": 1234,
    "from_id": 42,
    "wa_id": "573001234567",
    "wam_id": "wamid.HBgMNTczMDAxMjM0NTY3FQIAERgS…",
    "type": "out",
    "message_type": "template",
    "status": "failed",
    "error_code": 131047,
    "error_message": "Re-engagement message",
    "bsuid": null,
    "wa_username": null,
    "version": 1
  }
}

status_message es el único evento que le avisa que un envío no llegó. Si le importa la
entrega, actívelo.

send_bot_message tiene el payload anidado

Es la excepción de la familia: trae dos mensajes, el del contacto y la respuesta del bot.

{
  "event": "send_bot_message",
  "data": {
    "user": {
      "id": 900003,
      "type": "in",
      "message": "1",
      "wa_id": "573001234567",
      "bsuid": null,
      "wa_username": null,
      "...": "el resto del piso garantizado"
    },
    "bot": {
      "id": 900004,
      "type": "out",
      "message": "Perfecto, le comparto el catálogo 👇",
      "wa_id": "573001234567",
      "bsuid": null,
      "wa_username": null,
      "...": "el resto del piso garantizado"
    },
    "version": 1
  }
}

Cada uno de los dos objetos cumple el piso garantizado por su cuenta. version va en la
raíz, no dentro de user ni de bot.

// El acceso NO es data['message'], sino:
$textoDelContacto = $data['user']['message'] ?? null;
$respuestaDelBot  = $data['bot']['message'] ?? null;

Notas de comportamiento

receive_message no distingue de qué contacto viene por sender_id

sender_id es siempre null en los entrantes: identifica al agente que envía, no al
contacto. Para saber de quién es el mensaje use wa_id (más bsuid / wa_username).

Los mensajes de tipo system también disparan eventos

Wasapi registra notas internas —asignación de agente, cierre de conversación— como mensajes
con type: "system" y message_type: "system". Si su integración solo quiere conversación
real, fíltrelos:

if ($data['type'] === 'system') {
    return; // nota interna, no es un mensaje de la conversación
}

origin le dice quién originó el mensaje

Útil para no reprocesar sus propios envíos: un mensaje que usted mandó por la API vuelve
como send_message con origin: "api".

Ver también


Did this page help you?