# 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.

> **Antes de empezar:** Necesitas el plan Pro y ser propietario o admin del espacio de trabajo. Tu URL debe usar HTTPS y estar abierta a internet.

## 1. Abre los webhooks

En el menú lateral, ve a «Integraciones» y abre la pestaña «Webhooks».

![La pestaña de webhooks, antes de crear el primero.](https://iozen.ai/docs/shots/es/webhooks-tab-desktop.webp)

## 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:

```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:

```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](https://app.iozen.ai/docs/webhooks). Si prefieres crear y manejar endpoints desde tu código, usa la [API de ioZen](https://iozen.ai/es/docs/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.
