Webhooks — Introducción
Qué son, cómo se configuran y cómo se entregan los webhooks salientes de Wasapi.
Un webhook es un aviso que Wasapi envía a un servidor suyo cada vez que ocurre algo en su
cuenta: llega un mensaje, se crea un contacto, cambia el estado de un envío. En vez de que su
sistema pregunte cada tanto si hay novedades, Wasapi se las manda en el momento.
Esta sección documenta los webhooks salientes: los que Wasapi envía a suintegración. No cubre los webhooks entrantes de Meta hacia Wasapi.
Cómo se configura
- Entre a Configuración → Desarrolladores → Webhooks en la plataforma.
- Elija el evento que quiere recibir.
- Ponga la URL pública de su servidor.
- En los eventos que dependen de una línea, marque de qué números quiere recibirlos. En los
eventos de Instagram y Messenger, marque de qué canales: ahí el canal es obligatorio.
Puede tener varias URLs para un mismo evento: cada una recibe una copia.
El filtro por línea
De los 16 eventos de WhatsApp, 13 se filtran por número y 3 no. Los 4 eventos de Instagram y
Messenger se filtran por canal social, nunca por número (ver
Eventos de Instagram y Messenger):
| Se filtran por línea | No se filtran (aplican a toda la cuenta) |
|---|---|
receive_message, send_message, send_bot_message, status_message, status_bot_contact, wa_flow_sent, wa_flow_response_received, message_facebook_advertising, los cinco survey_* | create_contact, update_contact, delete_contact |
⚠️ Si conecta una línea nueva, no queda suscrita automáticamente. Los webhooks
existentes siguen apuntando a las líneas que usted marcó cuando los creó. Es la causa más
común de «configuré el webhook y no llega»: revise que la línea nueva esté marcada.
Cómo se entrega
| Método | POST |
| Content-Type | application/json |
| Cuerpo | El envoltorio {event, data} que se describe abajo |
| Timeout | 4 segundos |
| Intentos | 3 |
| Espera entre intentos | 10 s después del 1.º, 100 s después del 2.º |
| Firma | No se firma. No hay cabecera de firma que validar |
Su endpoint tiene 4 segundos
Wasapi corta la conexión a los 4 segundos y lo cuenta como fallo. Si su endpoint hace
trabajo pesado —consultar otra API, escribir en varias tablas, generar un PDF— no lo haga
dentro del request: guarde el payload, responda de inmediato y procese después, en
segundo plano.
Qué se considera entrega exitosa
Cualquier código 2xx. Cualquier otra cosa (4xx, 5xx, timeout, error de conexión) se
reintenta hasta 3 veces en total y después se descarta: no hay bandeja de reenvío ni
notificación de fallo.
Consecuencia práctica: si su servidor estuvo caído más de ~2 minutos, esos eventos se
perdieron. Para datos que no puede perder, complemente el webhook con una lectura periódica
de la API REST.
Reintentos ⇒ su endpoint debe ser idempotente
Un reintento envía el mismo evento otra vez. Si su servidor recibió el aviso pero
respondió tarde o con un 500 después de haber guardado, va a recibirlo repetido.
Use una llave natural para descartar duplicados:
| Familia | Llave sugerida |
|---|---|
| Mensajes | data.wam_id (el id del mensaje en Meta), o data.id |
| Contactos | data.uuid + event |
| Encuestas | data.session_id + event |
Tampoco hay garantía de orden. Dos eventos disparados con milisegundos de diferencia
pueden llegar al revés. No asuma que receive_message llega antes que el
update_contact que provocó.
Seguridad del endpoint
Como los webhooks no van firmados, cualquiera que descubra su URL puede enviarle datos
falsos. Recomendaciones:
- Use una URL larga y no adivinable, o agregue un token en el query string
(https://suservidor.com/wasapi?token=…) y valídelo. - Sirva el endpoint por HTTPS, con un certificado válido.
- Trate el contenido como dato no confiable: valide antes de insertar.
El envoltorio
Todos los eventos llegan con la misma forma:
{
"event": "receive_message",
"data": {
"...": "los campos del evento",
"version": 1
}
}event— el nombre del evento, igual al que activó en la plataforma.data— el contenido, distinto según la familia de evento.data.version— la versión del contrato. Hoy siempre1.
Los 20 eventos
| Evento | Cuándo se dispara | Familia |
|---|---|---|
receive_message | Entra un mensaje de un contacto | Mensaje |
send_message | Sale un mensaje desde la plataforma o la API | Mensaje |
send_bot_message | Un chatbot responde | Mensaje |
status_message | Cambia el estado de un mensaje enviado (entregado, leído, fallido) | Mensaje |
wa_flow_sent | Se envía un WhatsApp Flow | Mensaje |
wa_flow_response_received | El contacto responde un Flow | Mensaje |
create_contact | Se crea un contacto | Contacto |
update_contact | Se modifica un contacto | Contacto |
delete_contact | Se borra un contacto | Contacto |
status_bot_contact | Se activa o desactiva el bot para un contacto | Contacto |
message_facebook_advertising | Llega un mensaje desde un anuncio Click to WhatsApp | Publicidad |
survey_sent | Se envía una encuesta de satisfacción | Encuesta |
survey_started | El contacto empieza a responderla | Encuesta |
survey_response_received | Llega una respuesta | Encuesta |
survey_completed | La encuesta se completa | Encuesta |
survey_expired | La encuesta vence sin completarse | Encuesta |
social_message_received | Un cliente escribe por Instagram o Messenger | Social |
social_message_sent | Sale un mensaje por Instagram o Messenger, también si falla | Social |
social_message_status | El cliente lee un mensaje enviado por Instagram o Messenger | Social |
social_conversation_updated | Cambia el estado o el agente de una conversación social | Social |
Compatibilidad: qué podemos cambiar y qué no
La promesa: solo agregamos campos.Dentro de la v1 del contrato, un campo documentado no se quita ni cambia de significado.
Lo que sí puede pasar es que aparezcan campos nuevos.Si alguna vez hay que quitar o renombrar algo, habrá una v2 del payload y aviso
previo a las cuentas con integración activa, con las dos versiones conviviendo durante
la transición.
Qué significa esto para su integración: debe tolerar campos que no conoce. Es la única
condición para que un cambio aditivo no la rompa.
// ✅ Tome lo que necesita
$telefono = $data['phone'] ?? null;
// ❌ No inserte el payload completo a ciegas:
// un campo nuevo rompe el INSERT
$db->insert('mensajes', $data);Qué leer ahora
La página más importante es Identidad del contacto:
explica cómo saber a qué persona corresponde cada evento, incluido el caso de los contactos
que ocultan su número de teléfono.
Updated 11 days ago
