Guía · Integraciones y API
Recibe eventos de ioZen con webhooks
Un webhook avisa a tu sistema cuando algo pasa en ioZen, como un envío completado, un contacto nuevo o un caso que cambia de fase o de responsable. Así lo creas, lo pruebas y verificas su firma.
Un webhook es una URL tuya a la que ioZen llama cuando algo pasa en tu espacio de trabajo. Por ejemplo, cuando alguien termina el Intake Bot de tu carpintería, tu sistema recibe las respuestas al momento, sin tener que preguntarle a ioZen cada rato.
1. Abre los webhooks
En el menú lateral, ve a «Integraciones» y abre la pestaña «Webhooks».

2. Crea el endpoint
Toca «Crear Webhook» y completa:
- «URL del Endpoint»: la dirección de tu servidor que recibe los eventos. Empieza con
https://. - «Descripción»: opcional, para que tu equipo sepa para qué sirve.
- «Eventos»: marca al menos un evento.
- «Intake bots»: déjalo vacío para recibir eventos de todos tus bots, o marca solo los que te interesan.
Toca «Crear Webhook» para guardarlo.
3. Guarda el secreto
ioZen te muestra el «Secreto de Firma», que empieza con whsec_. Cópialo y guárdalo en tu servidor: no lo vas a ver de nuevo. Con él compruebas que cada llamada viene de ioZen.
4. Envía una prueba
En el menú del endpoint, toca «Enviar Prueba». En el teléfono, el botón está en la tarjeta del endpoint. ioZen envía al momento uno de los eventos que elegiste, con "is_test": true dentro de data, y te dice si tu servidor respondió bien.
Los eventos
| Evento | En la app | Cuándo llega |
|---|---|---|
submission.completed |
«Envío Completado» | Alguien termina un Intake Bot. |
contact.created |
«Contacto Creado» | Se crea un contacto nuevo. |
card.phase_changed |
«Caso Cambió de Fase» | Un caso pasa a otra fase de su Process Board. |
card.assigned |
«Caso Asignado» | Cambian los responsables de un caso, a mano o por una regla de reparto. |
Limita un endpoint a ciertos bots
Si marcas bots en «Intake bots», el endpoint recibe solo los eventos que vienen de esos bots. Así, una clínica puede mandar las citas a un sistema y las consultas de precios a otro.
Hay dos casos que conviene saber. Un contacto que alguien de tu equipo crea a mano no viene de ningún bot, así que llega a todos los endpoints que tienen «Contacto Creado». Y un caso que no nació de un bot llega solo a los endpoints sin bots marcados.
Qué recibe tu servidor
Cada evento llega como un POST con este cuerpo JSON:
{
"id": "evt_8f2c41d07a9b4e3f9a6d1c5b7e0f2a34",
"type": "submission.completed",
"api_version": "2026-08-27",
"created_at": "2026-11-06T15:04:05.000Z",
"data": {}
}data trae lo que pasó y cambia según el evento. La referencia completa muestra cada uno.
Junto con el cuerpo llegan estos encabezados:
| Encabezado | Qué trae |
|---|---|
X-IoZen-Event-Type |
El tipo de evento, igual que type. |
X-IoZen-Event-Id |
El id del evento, igual que id. |
X-IoZen-Timestamp |
Cuándo se firmó, en segundos Unix. |
X-IoZen-Signature |
La firma: sha256= seguido del HMAC-SHA256 en hexadecimal. |
Verifica la firma
ioZen firma cada llamada con tu secreto. La firma es el HMAC-SHA256 del texto timestamp.cuerpo: el valor de X-IoZen-Timestamp, un punto y el cuerpo tal como llegó. La llave es el secreto completo, con whsec_ incluido.
Calcula la firma con el cuerpo crudo, antes de convertirlo en JSON. Si lo conviertes y lo vuelves a escribir, la firma ya no coincide. En Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto'
// rawBody: el cuerpo tal como llegó (texto). secret: tu whsec_...
export function verifyIozenWebhook(rawBody, headers, secret) {
const timestamp = headers['x-iozen-timestamp']
const signature = headers['x-iozen-signature'] ?? ''
const expected =
'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`, 'utf8').digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature)
if (a.length !== b.length || !timingSafeEqual(a, b)) return false
// Rechaza llamadas viejas (aquí, de más de 5 minutos)
return Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
}Cada intento se firma de nuevo con la hora del momento, así que los reintentos pasan esta revisión.
Entregas, reintentos y pausa
Tu servidor tiene 10 segundos para responder con un código 2xx. Cualquier otra respuesta, una redirección o no responder a tiempo cuenta como fallo.
Si falla, reintentamos hasta 5 veces. Un reintento lleva el mismo id, así que úsalo para no procesar dos veces el mismo evento.
Si se juntan 10 intentos fallidos seguidos, pausamos el endpoint y enviamos un correo a los propietarios y admins. Un endpoint «Pausado» no recibe eventos. Cuando tu servidor vuelva a funcionar, toca «Reanudar».
En el menú del endpoint, «Entregas» muestra los intentos recientes, con su estado, el código HTTP y cuánto tardó. «Reenviar» manda ese evento otra vez al momento, armado con los datos actuales y con un id nuevo; la fila queda marcada «Manual».
Cambia o elimina un endpoint
En el menú del endpoint, «Editar» cambia los eventos, la descripción y los bots. La URL no se cambia: crea otro endpoint con la nueva. «Eliminar» lo borra y deja de enviarle eventos.
La referencia completa
La referencia de webhooks, con el contenido de data para cada evento, está en inglés en app.iozen.ai/docs/webhooks. Si prefieres crear y manejar endpoints desde tu código, usa la API de ioZen con el permiso «Gestionar Webhooks».
Preguntas frecuentes
- ¿Qué plan necesito para usar webhooks?
- Pro o superior. Cuántos endpoints puedes tener depende de tu plan; lo ves en Precios.
- ¿Un miembro del equipo puede crear webhooks?
- No. Solo el propietario o un admin del espacio de trabajo ven la pestaña de webhooks.
- ¿Qué pasa si mi servidor está caído?
- Reintentamos cada evento hasta 5 veces. Si se juntan 10 intentos fallidos seguidos, pausamos el endpoint y avisamos por correo a los propietarios y admins.
- ¿Puedo cambiar la URL de un endpoint?
- No. Crea un endpoint nuevo con la URL nueva y elimina el anterior.