> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visitoai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Webhooks — Visito M2M API

Las rutas son relativas a `/m2m/v1`. Envía `Authorization: Bearer <clave>`. La clave determina el tenant.

## Crear una suscripción

`POST /webhooks` requiere `webhooks:write` y el permiso de lectura de cada recurso seleccionado:

```json theme={null}
{"url":"https://integration.example.com/visito","events":["message.created","message.delivery.updated"]}
```

Guarda el `secret` devuelto: aparece solo al crear o rotar. `GET /webhooks` lista suscripciones. `GET/PATCH/DELETE /webhooks/{subscriptionId}` consulta, modifica o elimina. PATCH acepta `url`, `events` y `active`. `POST /webhooks/{subscriptionId}/rotate-secret` rota el secreto.

| Evento                       | Permiso de lectura |
| ---------------------------- | ------------------ |
| message.created              | conversations:read |
| message.delivery.updated     | messages:read      |
| conversation.handoff.updated | conversations:read |
| crm.opportunity.created      | crm:sales:read     |
| crm.opportunity.updated      | crm:sales:read     |

El evento contiene `eventId`, `type`, `schemaVersion:"1.0"`, `tenantId`, `occurredAt` y `data`. Los datos incluyen IDs, dirección/canal o cambios de estado/revisión. Consulta contenido con los GET autorizados. Se excluyen turnos internos y mensajes del playground.

## Verificar la firma

Encabezados: `X-Visito-Event-Id` y `X-Visito-Signature: t=<segundos-unix>,v1=<hmac-hex>`.

Calcula HMAC-SHA256 de `timestamp + "." + cuerpoOriginal` usando el secreto. Compara en tiempo constante, rechaza diferencias de más de cinco minutos y deduplica por eventId. Verifica los bytes originales antes de interpretar JSON.

## Entrega y recuperación

La entrega es al menos una vez y no garantiza orden. Devuelve 2xx después de aceptar durablemente el evento. No se siguen redirecciones. El destino debe usar HTTPS público en puerto 443 y DNS con IPv4 pública; no se admiten destinos locales, privados, reservados o solo IPv6. Se valida DNS y se fija la dirección de conexión en cada intento.

Cada intento tiene 10 segundos. Los errores se reintentan después de 1 minuto, 5 minutos, 30 minutos, 2 horas, 8 horas y 24 horas: siete intentos en total. Con `webhooks:read`, consulta `GET /webhooks/{subscriptionId}/deliveries` y su detalle `/{deliveryId}`. Para reiniciar un fallo agotado, usa `POST /webhooks/{subscriptionId}/deliveries/{deliveryId}/retry`; conserva eventId. Los metadatos se guardan 30 días después de terminar.

Una clave revocada/vencida/sin permisos o una suscripción desactivada/eliminada cancela entregas pendientes. Una solicitud HTTP en curso puede terminar. Reactivar no reenvía eventos cancelados. Modificar la suscripción la vincula a la clave que llama y vuelve a exigir permisos de lectura. La rotación del secreto aplica inmediatamente a futuros intentos.

En automatizaciones de respuesta, procesa solo `message.created` con `data.direction=inbound`. Persiste eventId y usa un `Idempotency-Key` estable al responder para evitar duplicados y bucles.
