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

# Webhooks

> Recibe eventos firmados con HMAC cuando algo pasa en tus agentes.

Cuando tus agentes terminan llamadas (o fallan), Ryvo te manda un evento a una URL que tú configuras. Útil para:

* Actualizar el deal en tu CRM con el resultado de la llamada.
* Disparar un follow-up automatizado si la llamada falló.
* Loggear el resumen de cada conversación en tu data warehouse.

## Configurar tu webhook

1. Entra a [app.ryvo.so/developers](https://app.ryvo.so/developers).
2. En la sección **Webhook**, agrega tu URL pública (HTTPS obligatorio).
3. Copia el **signing secret** que generamos. **Solo se muestra una vez** — guárdalo en un secret manager. Lo necesitas para verificar la firma de cada evento.

<Warning>
  La URL del webhook debe ser pública y aceptar peticiones `POST`. Si está detrás de un firewall o requiere auth, los eventos no van a llegar.
</Warning>

## Eventos disponibles

| Evento           | Cuándo se dispara                                   |
| ---------------- | --------------------------------------------------- |
| `call.initiated` | Cuando disparas una llamada vía `POST /v1/calls`.   |
| `call.completed` | Cuando termina exitosamente una llamada.            |
| `call.failed`    | Cuando una llamada termina con error o no contesta. |

Por defecto recibes **todos** los eventos. En el futuro vas a poder filtrar por tipo desde el portal.

## Shape de un evento

Todos los eventos comparten esta estructura:

```json theme={null}
{
  "id": "<uuid>",
  "type": "call.completed",
  "created_at": "2026-04-30T18:35:42.123Z",
  "data": {
    /* campos específicos por tipo de evento */
  }
}
```

### `call.initiated`

```json theme={null}
{
  "id": "evt_01abc...",
  "type": "call.initiated",
  "created_at": "2026-04-30T18:32:11.123Z",
  "data": {
    "id": "call_conv_01abcdef",
    "agent_id": "agent_8801kpabc123",
    "to": "+5215555550100",
    "metadata": {
      "lead_name": "Juan García"
    }
  }
}
```

### `call.completed`

```json theme={null}
{
  "id": "evt_02def...",
  "type": "call.completed",
  "created_at": "2026-04-30T18:35:44.987Z",
  "data": {
    "id": "call_conv_01abcdef",
    "agent_id": "agent_8801kpabc123",
    "status": "completed",
    "duration_seconds": 211,
    "transcript_summary": "El lead aceptó agendar una demo para el 5 de mayo a las 11am.",
    "termination_reason": "user_hung_up",
    "started_at": "2026-04-30T18:32:13Z",
    "ended_at": "2026-04-30T18:35:44Z"
  }
}
```

### `call.failed`

```json theme={null}
{
  "id": "evt_03ghi...",
  "type": "call.failed",
  "created_at": "2026-04-30T18:32:25.456Z",
  "data": {
    "id": "call_conv_01abcdef",
    "agent_id": "agent_8801kpabc123",
    "status": "failed",
    "reason": "no_answer"
  }
}
```

## Headers de cada delivery

| Header             | Valor                                            |
| ------------------ | ------------------------------------------------ |
| `Content-Type`     | `application/json`                               |
| `User-Agent`       | `Ryvo-Webhook/1.0`                               |
| `X-Ryvo-Signature` | `t=<unix_secs>,v1=<hex_sha256>` — verifica esto. |
| `X-Ryvo-Event`     | El tipo de evento (ej. `call.completed`).        |
| `X-Ryvo-Event-Id`  | UUID del evento — úsalo para idempotencia.       |
| `X-Ryvo-Delivery`  | UUID único de este intento de entrega.           |

## Reintentos automáticos

Si tu endpoint responde con un código que no es `2xx` o se cuelga más de 10 segundos, **reintentamos automáticamente** con backoff exponencial:

| Intento | Cuándo                             |
| ------- | ---------------------------------- |
| 1       | Inmediato cuando ocurre el evento. |
| 2       | 5 minutos después.                 |
| 3       | 30 minutos después.                |
| 4       | 2 horas después.                   |

Después del 4to intento fallido, marcamos la entrega como `exhausted` y no volvemos a intentar. Puedes ver el historial en `app.ryvo.so/developers`.

## Idempotencia del lado del receptor

Aunque hagamos retry solo en errores, **un mismo evento puede llegarte 2 veces** (ej. tu endpoint procesó OK pero la respuesta `200` se perdió en la red). Por eso te mandamos `X-Ryvo-Event-Id`: guarda los IDs procesados y descarta duplicados.

```javascript theme={null}
const processed = new Set() // en producción, usa Redis o tu DB

app.post("/webhook/ryvo", async (req, res) => {
  const eventId = req.headers["x-ryvo-event-id"]
  if (processed.has(eventId)) return res.sendStatus(200)
  processed.add(eventId)

  // ... tu lógica
  res.sendStatus(200)
})
```

## Responde rápido

Tu endpoint debe responder `200` en menos de 10 segundos. Si necesitas hacer trabajo lento (LLM call, sync a una API externa), responde `200` inmediato y procesa async (cola, background job).

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Verificar firmas" icon="signature" href="/webhooks/signature-verification">
    Cómo validar que el evento viene de Ryvo y no de un atacante.
  </Card>

  <Card title="Probar tu webhook" icon="flask" href="https://app.ryvo.so/developers">
    Desde el portal puedes mandarte un evento de prueba.
  </Card>
</CardGroup>
