Webhooksv1

Webhooks de CallSpace

En lugar de preguntar por la API, deja que CallSpace avise a tu sistema en cuanto ocurre algo: una llamada que acaba, un WhatsApp que entra o una factura emitida.

Qué son

Un webhook es una URL tuya a la que enviamos un POST cada vez que ocurre un evento que has elegido.

Tú eliges qué

Cada endpoint se suscribe solo a los eventos que le interesan.

Firmado

Cada envío HTTP lleva una firma HMAC-SHA256 para que puedas verificar que viene de nosotros.

Con reintentos

Si tu endpoint falla, reintentamos con espera creciente hasta 8 veces.

Requisitos del endpoint

La URL debe usar HTTPS y resolver a una IP pública. No seguimos redirecciones: un 3xx se trata como fallo definitivo. Responde con un 2xx en cuanto recibas el evento y procesa después: cortamos a los 10 segundos.

Dar de alta un endpoint

Se gestiona desde el dashboard, en Integraciones. Solo el owner del workspace puede hacerlo.

  1. 1

    Abre Integraciones → Webhooks y pulsa «Añadir webhook».

  2. 2

    Elige el tipo de destino: HTTP (JSON firmado) o Discord.

  3. 3

    Pega la URL y selecciona los eventos que quieres recibir.

  4. 4

    Guarda y copia el secreto de firma: lo necesitarás para verificar los envíos.

  5. 5

    Pulsa «Probar» para recibir un evento ping y confirmar que todo llega.

Catálogo de eventos

Los mensajes están separados por canal: message.* es solo SMS y whatsapp.* es solo WhatsApp, para que puedas suscribirte a uno sin recibir el otro.

Llamadas

call.startedUna llamada empieza a sonar.
call.answeredAlguien descuelga la llamada.
call.completedLa llamada termina con normalidad.
call.missedNadie contestó la llamada (perdida, ocupado o sin respuesta).
call.recording_readyLa grabación se ha archivado y su URL definitiva ya es estable.

Mensajería

message.receivedEntra un SMS.
message.sentSe acepta un SMS para envío.
whatsapp.receivedEntra un mensaje de WhatsApp.
whatsapp.sentSe acepta un mensaje de WhatsApp para envío.
conversation.assignedUna conversación se asigna a un agente distinto.

Agentes y colas

agent.status_changedCambia la disponibilidad, el estado de routing o la conexión de un agente.
queue.call_waitingUna llamada entra en cola.
queue.call_connectedUna llamada en cola se conecta con un agente.
queue.call_abandonedEl llamante cuelga antes de ser atendido.

Contactos y facturación

contact.createdSe crea un contacto nuevo.
invoice.issuedSe emite una factura del periodo cerrado.

Payload y cabeceras

Todos los endpoints HTTP reciben el mismo sobre versionado. Los datos propios de cada evento van dentro de data.

json
{
  "id": "8f14e45f-ceea-467a-9f6e-4f2c1e0b3a77",
  "eventId": "b1c2d3e4-5f60-4a7b-8c9d-0e1f2a3b4c5d",
  "event": "call.completed",
  "version": 1,
  "createdAt": "2026-08-24T10:00:00.000Z",
  "workspaceId": "2c8a1f6e-7b90-4d3a-8e5f-1a2b3c4d5e6f",
  "data": {
    "callId": "a3d9c1b2-4e5f-4a6b-9c8d-7e6f5a4b3c2d",
    "callSid": "CA1234567890abcdef1234567890abcd",
    "direction": "inbound",
    "status": "completed",
    "from": "+34600000000",
    "to": "+34911223344",
    "durationSeconds": 128
  }
}
CabeceraSignificado
x-callspace-eventNombre del evento, p. ej. call.completed.
x-callspace-event-idId del hecho de negocio, común a todos tus endpoints.
x-callspace-delivery-idId de esta entrega concreta.
idempotency-keyIgual al delivery id. Estable entre reintentos: úsalo para deduplicar.
x-callspace-attemptNúmero de intento, empezando en 1.
x-callspace-timestampSegundos Unix en los que se firmó el envío.
x-callspace-signaturet=<timestamp>,v1=<hmac-sha256 en hex>.

Entrega al menos una vez

Preferimos repetir un evento antes que perderlo, así que tu endpoint puede recibir el mismo envío más de una vez (por ejemplo, si respondes tarde y el reintento ya salió). Deduplica por idempotency-key, que es constante a lo largo de todos los intentos de una misma entrega.

Verificar la firma

Comprueba siempre la firma antes de actuar sobre un evento: es lo único que distingue un envío nuestro de uno de cualquiera que conozca tu URL.

javascript
import crypto from 'node:crypto';

// El HMAC cubre "<timestamp>.<cuerpo crudo>". Necesitas el BODY SIN PARSEAR:
// si tu framework ya lo convirtió a objeto, vuelve a serializarlo y la firma
// dejará de cuadrar.
export function verifyCallspaceWebhook(rawBody, headers, secret) {
    const header = headers['x-callspace-signature'] ?? '';
    const parts = Object.fromEntries(
        header.split(',').map((part) => part.trim().split('=')),
    );

    const timestamp = Number.parseInt(parts.t, 10);
    if (!Number.isFinite(timestamp)) return false;

    // Rechaza reenvíos antiguos.
    const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp);
    if (age > 300) return false;

    const expected = crypto
        .createHmac('sha256', secret)
        .update(`${timestamp}.${rawBody}`)
        .digest('hex');

    // Comparación en tiempo constante: un === filtraría el secreto por timing.
    const a = Buffer.from(expected, 'utf8');
    const b = Buffer.from(parts.v1 ?? '', 'utf8');
    return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Reintentos y auto-desactivación

Qué pasa cuando tu endpoint no responde como esperamos.

Reintentamos

Ante errores de red, timeouts y respuestas 5xx, 408 o 429. La espera crece: ~10s, 30s, 90s, 4.5m, 13.5m, 40m, 2h, hasta un tope de 6h, con un margen aleatorio para no saturarte cuando te recuperes.

No reintentamos

Ante un 4xx que no sea 408 o 429, y ante un 3xx. Son errores de configuración que no se arreglan repitiendo el envío.

Auto-desactivación

Tras 20 fallos definitivos seguidos, el endpoint se desactiva y deja de recibir eventos. Lo verás marcado en el dashboard y podrás reactivarlo desde ahí una vez arreglado. Cualquier entrega correcta pone el contador a cero.

Cada envío queda registrado en el historial de entregas del endpoint, con el código HTTP, el número de intento y el payload exacto. Desde ahí puedes reenviar manualmente cualquier entrega.

Destino Discord

Si lo que quieres es ver los eventos en un canal, elige el tipo Discord y te los formateamos como embeds legibles.

En Discord, crea un webhook desde Ajustes del canal → Integraciones → Webhooks y pega aquí la URL que te da.

Estos destinos no llevan firma HMAC: en Discord la propia URL es la credencial, así que trátala como un secreto. Si se filtra, bórrala en Discord y crea otra.