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.
Puede tener varias URLs para un mismo evento: cada una recibe una copia.
El filtro por línea
De los 16 eventos, 13 se filtran por número de WhatsApp y 3 no:
| 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 16 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 |
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 17 days ago
