Skip to main content
Los webhooks permiten recibir notificaciones HTTP en tiempo real cuando ocurren eventos en tu workspace. Solo se entregan eventos para agentes y suscripciones activas.
Requiere permisos webhooks:read para consultas y webhooks:write para crear, actualizar o eliminar. Todos los endpoints requieren el header X-Workspace-Id.

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

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);

Listar webhooks

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);
}

Obtener webhook

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);

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

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);
}

Obtener un delivery por ID

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);

Actualizar webhook

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);

Eliminar webhook

await client.webhooks.delete('webhook-id-uuid');
console.log('Webhook eliminado');

Gestión de suscripciones

Crear suscripción a evento

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);

Listar suscripciones

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'})`);
}

Obtener suscripción

const sub = await client.webhooks.getSubscription(
  'webhook-id-uuid',
  'subscription-id-uuid',
);

console.log('Evento:', sub.eventKey);
console.log('Activa:', sub.isActive);

Actualizar suscripción

const updated = await client.webhooks.updateSubscription(
  'webhook-id-uuid',
  'subscription-id-uuid',
  {
    isActive: false,
  },
);

console.log('Suscripción actualizada');

Eliminar suscripción

await client.webhooks.deleteSubscription(
  'webhook-id-uuid',
  'subscription-id-uuid',
);
console.log('Suscripción eliminada');

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.
{
  "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.
{
  "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.
{
  "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.
{
  "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:
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:
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:
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:
const webhook = await client.webhooks.create({
  url: 'https://mi-servidor.com/webhook',
  agentId: 'agent-uuid-12345', // Solo eventos de este agente
  // ...
});