Webhooks
Recibí un POST con JSON cuando cambian los datos de tu cuenta. Usá los eventos para activar tu proceso y la API para consultar o reconciliar el estado actual.
Crear una suscripción
Desde el panel: Ajustes → Integraciones → Webhooks → Agregar. Necesitás permisos de administrador.
Por API, usando las variables de la introducción:
curl --fail-with-body --request POST \
"$CRM_BASE_URL/api/v1/accounts/$CRM_ACCOUNT_ID/webhooks" \
--header "api_access_token: $CRM_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"webhook": {
"url": "https://tu-sistema.example/hooks/crm/SECRETO_ALEATORIO",
"subscriptions": ["conversation_created", "conversation_updated",
"conversation_status_changed", "message_created", "message_updated"]
}
}'
El objeto webhook es obligatorio. Reemplazá la URL por tu receptor HTTPS y un secreto aleatorio propio. Para revisar las suscripciones usá GET .../webhooks; para modificar una, PATCH .../webhooks/{id} con el mismo envoltorio; para eliminarla, DELETE .../webhooks/{id}.
Eventos disponibles
| Evento | Cuándo se emite | Recurso |
|---|---|---|
conversation_created |
Se crea una conversación; una reapertura no es una creación. | Conversación. |
conversation_updated |
Cambian atributos observados de la conversación. | Conversación y changed_attributes. |
conversation_status_changed |
Cambia status. |
Conversación y changed_attributes. |
message_created |
Se crea un mensaje entrante, saliente o de tipo template; incluye notas privadas. | Mensaje. |
message_updated |
Se actualiza un mensaje enviable por webhook, por ejemplo su contenido o estado. | Mensaje. |
contact_created / contact_updated |
Se crea o actualiza un contacto. | Contacto. |
inbox_created / inbox_updated |
Se crea o actualiza una bandeja. | Bandeja. |
webwidget_triggered |
Se dispara el evento del widget web. | Contacto, bandeja e información del evento. |
Los mensajes de actividad no se envían por
message_creatednimessage_updated. Para cambios de estado o asignación usá eventos de conversación; para leer las actividades, consultá el historial por API. Algunos conectores guardan cambios sin emitir eventos: programá reconciliaciones si necesitás consistencia.
Ejemplo de conversación
Payload abreviado de conversation_status_changed:
{
"event": "conversation_status_changed",
"id": 4821,
"account": { "id": 123, "name": "Mi empresa" },
"inbox_id": 12,
"channel": "Channel::Whatsapp",
"status": "resolved",
"labels": ["presupuesto-enviado"],
"custom_attributes": {},
"additional_attributes": {},
"meta": {
"sender": { "id": 991, "name": "Ana Pérez", "type": "contact" },
"assignee": { "id": 7, "name": "Lucía", "type": "user" }
},
"messages": [],
"changed_attributes": [
{ "status": { "previous_value": "open", "current_value": "resolved" } }
],
"created_at": 1788357600
}
changed_attributes es un array de objetos, no un objeto único. created_at es la fecha de creación de la conversación; no identifica cuándo ocurrió cada actualización. messages contiene como máximo un mensaje del chat, no el historial completo.
meta.assignee es el responsable actual, no necesariamente quien hizo el cambio. El actor de un cambio no está garantizado como campo estructurado del webhook.
Ejemplo de mensaje
{
"event": "message_created",
"id": 88210,
"account": { "id": 123, "name": "Mi empresa" },
"inbox": { "id": 12, "name": "Atención" },
"conversation": { "id": 4821, "status": "open", "inbox_id": 12 },
"content": "Hola, necesito un presupuesto",
"content_type": "text",
"content_attributes": {},
"message_type": "incoming",
"private": false,
"status": "sent",
"sender": { "id": 991, "name": "Ana Pérez", "phone_number": "+5491155551234" },
"created_at": "2026-09-02T14:00:00.000Z"
}
El ejemplo recorta los objetos anidados. En eventos de mensaje, message_type es texto; por API y dentro de conversation.messages es numérico. El sender de un contacto no incluye necesariamente type: para reconocer mensajes del cliente usá message_type: "incoming". Usuarios y bots pueden incluir sender.type: "user" o "agent_bot"; una integración que usa un token de usuario figura como ese usuario.
Para evitar bucles, un receptor que responde al cliente debe filtrar por event === "message_created", message_type === "incoming" y private === false. No respondas a tus propios mensajes salientes.
Entrega y reintentos
Respondé con 2xx después de guardar el evento en una cola o almacenamiento duradero, y procesalo en segundo plano. El timeout predeterminado es de 5 segundos y puede variar por instalación.
| Resultado del receptor | Política actual |
|---|---|
2xx |
Entrega aceptada. |
502, 503, 504, timeout de conexión, conexión rechazada y ciertos errores de red |
Hasta 7 reintentos, con esperas base de 5, 10, 20, 40, 80, 120 y 120 segundos. La cola puede agregar demora. |
| Timeout de lectura (no llega la respuesta) | Un reintento corto; después no se vuelve a enviar automáticamente. El receptor pudo haber procesado el primer intento. |
400, 401, 403, 404, 405, URL inválida o ciertos fallos de resolución |
Se descarta sin reintentar. |
Otros errores, como 500, 422, 429 |
Cola general con espera creciente, limitada por el máximo del job (7 reintentos) y la configuración del servidor. |
Estas esperas no son un SLA de entrega. Un webhook puede agotar sus intentos: no lo trates como una copia infalible de todos los cambios. Usá la API para recuperar y reconciliar datos.
Duplicados y orden
Los eventos pueden llegar repetidos y fuera de orden. No hay un identificador único de entrega documentado.
- Para creaciones de mensajes, podés registrar
(account.id, event, id)como clave de procesamiento. - Para actualizaciones, no dedupliques de forma permanente por
id + event: el mismo mensaje puede pasar desentadeliveredy aread; la misma conversación puede abrirse y resolverse varias veces. - Para mantener una copia de recursos, usá el webhook como aviso, agrupá avisos cercanos y consultá el estado actual por API antes de persistirlo.
- Un hash temporal del payload puede reducir entregas idénticas, pero no representa una versión del recurso ni distingue todos los cambios legítimos.
No ordenes actualizaciones por created_at: esa fecha corresponde al recurso. Si ejecutás acciones irreversibles a partir de eventos, definí tu propia idempotencia de negocio y verificá el estado.
Seguridad del receptor
Estos webhooks no incluyen una firma criptográfica verificable. Un secreto en la URL reduce accesos no autorizados, pero no equivale a una firma del contenido.
Usá HTTPS, compará el secreto recibido y evitá registrar la URL completa. Validá account.id y el tipo de evento. Antes de una acción sensible, confirmá los datos con tu token a través de la API. El receptor puede recibir conversaciones, datos de contacto y notas privadas: limitá quién puede acceder a ellos.
Probar antes de conectar tu sistema
- Registrá el webhook con una URL de prueba controlada por vos.
- Enviá un mensaje desde un contacto de prueba y verificá
message_created. - Respondé desde el CRM y comprobá que tu filtro ignore el mensaje saliente.
- Cambiá el estado y verificá el array
changed_attributes. - Reproducí el mismo payload dos veces en tu receptor y verificá que no duplique la acción.
- Simulá un error temporal del receptor y comprobá la recuperación.
Usá datos ficticios si inspeccionás eventos con un servicio externo de captura de webhooks.