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

# Webhooks

> Configure webhooks to receive real-time notifications when events occur in your workspace

<Note>
  Los webhooks permiten recibir notificaciones HTTP en tiempo real cuando
  ocurren eventos en tu workspace. Solo se entregan eventos para agentes y
  suscripciones activas.
</Note>

<Info>
  Requiere permisos <code>webhooks:read</code> para consultas y{' '}
  <code>webhooks:write</code> para crear, actualizar o eliminar. Todos los
  endpoints requieren el header <code>X-Workspace-Id</code>.
</Info>

## Eventos disponibles

Los webhooks pueden suscribirse a los siguientes eventos:

### Eventos de Tools

* **tool.invoked**: Se ejecuta cuando un agente termina de invocar una herramienta y recibe una respuesta.

### Eventos de llamadas (Calls)

* **call.callStarted**: Una llamada de voz comenzó para uno de los agentes del workspace.
* **call.callEnded**: Una llamada de voz finalizó (incluye URLs de grabación y transcripción).
* **call.callMissed**: Una llamada no fue contestada antes del timeout.
* **call.voicemailReceived**: Un interlocutor dejó un mensaje de buzón de voz.

### Eventos de WhatsApp

* **whatsapp.messageReceived**: Un usuario de WhatsApp envió un mensaje entrante al workspace.
* **whatsapp.messageDelivered**: Un mensaje saliente de WhatsApp llegó al dispositivo del destinatario.
* **whatsapp.messageFailed**: Un mensaje saliente de WhatsApp falló al entregarse.
* **whatsapp.conversationStarted**: La plataforma abrió una nueva sesión de conversación de WhatsApp.
* **whatsapp.conversationEnded**: Una sesión de conversación de WhatsApp existente se cerró.

## Seguridad

Todas las entregas de webhook incluyen:

* **HTTPS requerido**: Solo se aceptan URLs HTTPS.
* **Firma HMAC-SHA256**: Cada request incluye el header `x-signature` con la firma del payload.
* **Timestamp**: Header `x-timestamp` con marca temporal ISO-8601.
* **Secreto**: El secreto se genera automáticamente (mínimo 16 caracteres) o puedes proveer el tuyo.

### Verificación de firmas

Para validar que el webhook proviene de Agents Studio, verifica la firma HMAC-SHA256:

```typescript theme={null}
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  timestamp: string,
  secret: string,
): boolean {
  const message = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature),
  );
}

// Uso en un endpoint Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'] as string;
  const timestamp = req.headers['x-timestamp'] as string;
  const payload = req.body.toString('utf-8');

  if (
    !verifyWebhookSignature(
      payload,
      signature,
      timestamp,
      process.env.WEBHOOK_SECRET!,
    )
  ) {
    return res.status(401).send('Invalid signature');
  }

  // Procesar el evento
  const event = JSON.parse(payload);
  console.log('Event received:', event.event);

  res.status(200).send('OK');
});
```

## Gestión de webhooks

### Crear webhook

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { createClient } from '@getsupervisor/agents-studio-sdk';

    const client = createClient({
      baseUrl: process.env.API_BASE_URL!,
      workspaceId: process.env.WORKSPACE_ID!,
      apiKey: process.env.API_KEY!,
    });

    const webhook = await client.webhooks.create({
      url: 'https://mi-servidor.com/webhooks/agents-studio',
      agentId: '00000000-0000-4000-8000-000000000301', // Opcional: eventos solo de este agente
      description: 'Webhook para notificaciones de llamadas',
      method: 'POST', // 'GET' o 'POST' (por defecto: 'POST')
      isActive: true,
      headers: {
        'X-Custom-Header': 'valor',
      },
      // secret: 'mi-secreto-de-16-caracteres-o-mas', // Opcional: se genera automáticamente si se omite
    });

    console.log('Webhook creado:', webhook.id);
    console.log('Secreto (preview):', webhook.secretPreview);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "$API_BASE_URL/v1/webhooks" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://mi-servidor.com/webhooks/agents-studio",
        "description": "Webhook para notificaciones de llamadas",
        "method": "POST",
        "isActive": true,
        "headers": {
          "X-Custom-Header": "valor"
        }
      }'
    ```
  </Tab>
</Tabs>

### Listar webhooks

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { createClient } from '@getsupervisor/agents-studio-sdk';

    const client = createClient({
      baseUrl: process.env.API_BASE_URL!,
      workspaceId: process.env.WORKSPACE_ID!,
      apiKey: process.env.API_KEY!,
    });

    const page1 = await client.webhooks.list({
      page: 1,
      limit: 20,
      sort: '-createdAt',
    });

    console.log(`Total: ${page1.meta.total} webhooks`);

    for (const webhook of page1.data) {
      console.log(`- ${webhook.url} (${webhook.isActive ? 'activo' : 'inactivo'})`);
    }

    // Paginación automática
    if (page1.meta.hasNext) {
      const page2 = await page1.next();
      console.log('Siguiente página:', page2?.meta.page);
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks?page=1&limit=20&sort=-createdAt" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

### Obtener webhook

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const webhook = await client.webhooks.get('webhook-id-uuid');

    console.log('URL:', webhook.url);
    console.log('Método:', webhook.method);
    console.log('Entregas exitosas:', webhook.successCount);
    console.log('Entregas fallidas:', webhook.failureCount);
    console.log('Última entrega:', webhook.lastDeliveryAt);
    console.log('Headers personalizados:', webhook.headers);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks/WEBHOOK_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

## Deliveries (historial de entregas)

Los deliveries representan cada intento de entrega de un evento a tu endpoint (incluye status, intentos, error y response status).

### Listar deliveries de un webhook

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const deliveries = await client.webhooks.listDeliveries('WEBHOOK_ID', {
      page: 1,
      limit: 50,
      // Ejemplo: filtrar por fallidos de un evento específico
      filter: 'and(eq(status,"failed"),eq(eventKey,"call.callEnded"))',
    });

    for (const d of deliveries.data) {
      console.log(d.id, d.status, d.responseStatus, d.lastError);
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/deliveries?page=1&limit=50&filter=and(eq(status,\"failed\"),eq(eventKey,\"call.callEnded\"))" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

### Obtener un delivery por ID

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const delivery = await client.webhooks.getDelivery('WEBHOOK_ID', 'DELIVERY_ID');

    console.log('Status:', delivery.status);
    console.log('Response status:', delivery.responseStatus);
    console.log('Last error:', delivery.lastError);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/deliveries/DELIVERY_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

### Actualizar webhook

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const updated = await client.webhooks.update('webhook-id-uuid', {
      url: 'https://nuevo-servidor.com/webhook',
      isActive: false,
      method: 'GET',
      description: 'Webhook actualizado',
    });

    console.log('Webhook actualizado:', updated.id);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X PATCH "$API_BASE_URL/v1/webhooks/WEBHOOK_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "isActive": false,
        "description": "Webhook actualizado"
      }'
    ```
  </Tab>
</Tabs>

### Eliminar webhook

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    await client.webhooks.delete('webhook-id-uuid');
    console.log('Webhook eliminado');
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X DELETE "$API_BASE_URL/v1/webhooks/WEBHOOK_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

## Gestión de suscripciones

### Crear suscripción a evento

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const subscription = await client.webhooks.createSubscription(
      'webhook-id-uuid',
      {
        eventKey: 'call.callEnded',
        isActive: true,
      },
    );

    console.log('Suscripción creada:', subscription.id);
    console.log('Evento:', subscription.eventKey);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/subscriptions" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "eventKey": "call.callEnded",
        "isActive": true
      }'
    ```
  </Tab>
</Tabs>

### Listar suscripciones

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const subs = await client.webhooks.listSubscriptions('webhook-id-uuid', {
      page: 1,
      limit: 50,
    });

    for (const sub of subs.data) {
      console.log(`- ${sub.eventKey} (${sub.isActive ? 'activo' : 'inactivo'})`);
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/subscriptions?page=1&limit=50" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

### Obtener suscripción

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const sub = await client.webhooks.getSubscription(
      'webhook-id-uuid',
      'subscription-id-uuid',
    );

    console.log('Evento:', sub.eventKey);
    console.log('Activa:', sub.isActive);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/subscriptions/SUBSCRIPTION_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

### Actualizar suscripción

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const updated = await client.webhooks.updateSubscription(
      'webhook-id-uuid',
      'subscription-id-uuid',
      {
        isActive: false,
      },
    );

    console.log('Suscripción actualizada');
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X PATCH "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/subscriptions/SUBSCRIPTION_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "isActive": false
      }'
    ```
  </Tab>
</Tabs>

### Eliminar suscripción

<Tabs>
  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    await client.webhooks.deleteSubscription(
      'webhook-id-uuid',
      'subscription-id-uuid',
    );
    console.log('Suscripción eliminada');
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X DELETE "$API_BASE_URL/v1/webhooks/WEBHOOK_ID/subscriptions/SUBSCRIPTION_ID" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Workspace-Id: $WORKSPACE_ID"
    ```
  </Tab>
</Tabs>

## Estructura de payloads

### Evento: call.callEnded

Se dispara cuando una llamada de voz finaliza. Incluye información del objetivo alcanzado, URLs de grabación y transcripción.

```json theme={null}
{
  "event": "call.callEnded",
  "timestamp": "2025-01-18T10:30:00.000Z",
  "workspaceId": "00000000-0000-4000-8000-000000000100",
  "agentId": "00000000-0000-4000-8000-000000000301",
  "callId": "call-uuid-12345",
  "status": "ended",
  "sipCode": 180,
  "endReason": "dial_no_answer",
  "durationMs": 45000,
  "result": {
    "goal": {
      "achieved": true,
      "reason": "Customer inquiry resolved successfully"
    },
    "recordingRoute": "https://storage.example.com/recordings/call-uuid-12345.mp3",
    "transcriptionRoute": "https://storage.example.com/transcriptions/call-uuid-12345.json"
  },
  "metadata": {
    "toNumber": "+1234567890",
    "createdAt": "2025-01-18T10:29:15.000Z",
    "executionId": "execution-uuid-67890"
  }
}
```

### Evento: call.callStarted

Se dispara cuando una llamada de voz comienza.

```json theme={null}
{
  "event": "call.callStarted",
  "timestamp": "2025-01-18T10:29:15.000Z",
  "workspaceId": "00000000-0000-4000-8000-000000000100",
  "agentId": "00000000-0000-4000-8000-000000000301",
  "callId": "call-uuid-12345",
  "status": "started",
  "metadata": {
    "toNumber": "+1234567890",
    "direction": "outbound",
    "executionId": "execution-uuid-67890"
  }
}
```

### Evento: tool.invoked

Se dispara cuando un agente termina de ejecutar una herramienta.

```json theme={null}
{
  "event": "tool.invoked",
  "timestamp": "2025-01-18T10:30:00.000Z",
  "workspaceId": "00000000-0000-4000-8000-000000000100",
  "agentId": "00000000-0000-4000-8000-000000000301",
  "toolId": "tool-uuid-54321",
  "toolIdentifier": "search_knowledge_base",
  "executionId": "execution-uuid-67890",
  "result": {
    "success": true,
    "output": "Query results here..."
  }
}
```

### Evento: whatsapp.messageReceived

Se dispara cuando se recibe un mensaje entrante de WhatsApp.

```json theme={null}
{
  "event": "whatsapp.messageReceived",
  "timestamp": "2025-01-18T10:30:00.000Z",
  "workspaceId": "00000000-0000-4000-8000-000000000100",
  "agentId": "00000000-0000-4000-8000-000000000301",
  "messageId": "wamid.12345",
  "from": "+1234567890",
  "message": {
    "type": "text",
    "text": {
      "body": "Hola, necesito ayuda"
    }
  },
  "metadata": {
    "conversationId": "conversation-uuid-11111"
  }
}
```

## Mejores prácticas

### 1. Procesar eventos de forma idempotente

Los webhooks pueden ser reenviados en caso de fallos. Asegúrate de que tu endpoint pueda procesar el mismo evento múltiples veces sin efectos secundarios:

```typescript theme={null}
const processedEvents = new Set<string>();

app.post('/webhook', async (req, res) => {
  const event = req.body;
  const eventId = `${event.event}-${event.timestamp}-${event.callId || event.messageId}`;

  if (processedEvents.has(eventId)) {
    console.log('Event already processed:', eventId);
    return res.status(200).send('OK');
  }

  // Procesar el evento
  await processEvent(event);

  processedEvents.add(eventId);
  res.status(200).send('OK');
});
```

### 2. Responder rápidamente

Responde con `2xx` lo antes posible y procesa el evento de forma asíncrona:

```typescript theme={null}
app.post('/webhook', async (req, res) => {
  const event = req.body;

  // Responder inmediatamente
  res.status(200).send('OK');

  // Procesar asíncronamente
  setImmediate(async () => {
    try {
      await processEvent(event);
    } catch (error) {
      console.error('Error processing event:', error);
    }
  });
});
```

### 3. Reintentos automáticos

El sistema reintenta entregas fallidas hasta 5 veces con backoff exponencial:

* Intento 1: inmediato
* Intento 2: \~1 segundo después
* Intento 3: \~2 segundos después
* Intento 4: \~4 segundos después
* Intento 5: \~8 segundos después

Solo respuestas con código `2xx` se consideran exitosas.

### 4. Monitorear entregas

Usa los campos `successCount`, `failureCount` y `lastDeliveryAt` del webhook para monitorear la salud:

```typescript theme={null}
const webhook = await client.webhooks.get('webhook-id');

if (webhook.failureCount > 10) {
  console.warn(`Webhook ${webhook.id} has ${webhook.failureCount} failures`);
}

if (webhook.lastDeliveryAt) {
  const hoursSinceLastDelivery =
    (Date.now() - new Date(webhook.lastDeliveryAt).getTime()) /
    (1000 * 60 * 60);

  if (hoursSinceLastDelivery > 24) {
    console.warn(`No deliveries in ${hoursSinceLastDelivery.toFixed(1)} hours`);
  }
}
```

### 5. Filtrar por agente

Si solo necesitas eventos de un agente específico, configura `agentId` al crear el webhook:

```typescript theme={null}
const webhook = await client.webhooks.create({
  url: 'https://mi-servidor.com/webhook',
  agentId: 'agent-uuid-12345', // Solo eventos de este agente
  // ...
});
```
