CallwiseDocs

Contrato del servicio de voz

El webhook firmado que el servicio de voz invoca para crear llamadas, agregar turnos de la transcripción y cerrar llamadas.

Solo el contrato

Esto es un contrato. El servicio de voz en Python (voz a texto, el modelo de lenguaje y texto a voz por WebRTC y SIP) aún no está construido. El lado de Callwise del webhook está construido y probado; nada lo invoca en producción.

El servicio de voz es el único que escribe los registros de llamadas. Informa cada llamada a la aplicación como una serie de eventos. La fuente de verdad de la carga es el esquema zod voiceEventSchema en apps/web/lib/callwise/voice-events.ts.

Endpoint

POST /api/voice/events
Content-Type: application/json
x-callwise-timestamp: 1760530212
x-callwise-signature: 5f0c2e…(64 hex characters)

Firma

EncabezadoValor
x-callwise-timestampTiempo Unix en segundos en el momento en que se firmó la solicitud
x-callwise-signatureHMAC-SHA256 en hexadecimal de `${timestamp}.${rawBody}` con el secreto compartido CALLWISE_VOICE_SECRET

Firme los bytes exactos que envía. La aplicación vuelve a calcular la firma sobre el cuerpo sin procesar antes de analizarlo.

import { createHmac } from 'node:crypto';

const timestamp = Math.floor(Date.now() / 1000).toString();
const rawBody = JSON.stringify(event);
const signature = createHmac('sha256', process.env.CALLWISE_VOICE_SECRET!)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');
import hmac, hashlib, json, os, time

timestamp = str(int(time.time()))
raw_body = json.dumps(event, separators=(",", ":"))
signature = hmac.new(
    os.environ["CALLWISE_VOICE_SECRET"].encode(),
    f"{timestamp}.{raw_body}".encode(),
    hashlib.sha256,
).hexdigest()

Las solicitudes cuya marca de tiempo se aleja más de 5 minutos del reloj del servidor se rechazan, lo que impide las repeticiones de solicitudes antiguas.

Respuestas

EstadoCuándo
200 { "ok": true, "duplicate": false }Evento aplicado
200 { "ok": true, "duplicate": true }Un evento con este id ya se había aplicado; no cambió nada
400El cuerpo no es JSON válido o no coincide con el esquema (por ejemplo, un tipo de evento desconocido)
401Falta la firma o es incorrecta, o la marca de tiempo está fuera de la ventana de 5 minutos

Idempotencia

Cada evento tiene su propio id. Enviar el mismo evento dos veces es seguro: el segundo se confirma con duplicate: true y se ignora. Reintente ante errores de red y 5xx con el mismo id.

Forma del evento

Todos los eventos tienen el mismo sobre:

CampoTipoNotas
idstring, 1–200 caracteresId del evento, único por evento
typecall.started | call.turn | call.ended
accountIduuidEl espacio de trabajo (cuenta de equipo)
callobjectcall.id (uuid) es obligatorio. El servicio de voz lo elige y usa el mismo id en cada evento de esa llamada.
turnobjectSolo en call.turn

call.started

Crea una llamada en vivo. Quien llama se busca por número de teléfono o se crea. Si se omite agentId, el agente se elige a partir del número que se marcó.

{
  "id": "evt_01JA8S0K6Q2M3",
  "type": "call.started",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": {
    "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33",
    "to": "+15552108842",
    "callerPhone": "+15551237890",
    "callerName": "Emily Johnson",
    "direction": "inbound",
    "startedAt": "2026-10-15T13:41:02Z",
    "isTest": false,
    "afterHours": false
  }
}

Campos opcionales de call: numberId (uuid) o to (E.164), agentId, locationId, callerPhone, callerName, direction (inbound o outbound), startedAt (ISO 8601), isTest, afterHours.

call.turn

Agrega una línea de la transcripción. call solo lleva el id. Los turnos se guardan por (call id, seq), así que reenviar un turno lo reemplaza.

{
  "id": "evt_01JA8S0M1T7Z4",
  "type": "call.turn",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": { "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33" },
  "turn": {
    "seq": 1,
    "atMs": 4200,
    "speaker": "caller",
    "text": "Hi, I need a cleaning next week."
  }
}
Campo de turnTipo
seqentero ≥ 0, orden dentro de la llamada
atMsentero ≥ 0, milisegundos desde que empezó la llamada
speakeragent | caller | staff | system
textstring

call.ended

Cierra la llamada. Los minutos facturados son ceil(durationS / 60) y se suman al uso del día. La aplicación cobra entonces créditos por la llamada: minutos facturados × la tarifa de la pila de modelos del agente + un resumen (consulte Modelos de IA y créditos), tomados de la bolsa de créditos compartida. Si se omite summary, se completa un resumen de plantilla.

En call.started, la respuesta le indica al servicio de voz cómo enrutar la llamada: route: "agent", o route: "fallback" con un reason cuando la bolsa de créditos, el límite de concurrencia o el límite por hora dicen que no. Las llamadas nunca se descartan.

{
  "id": "evt_01JA8S2B9W0K8",
  "type": "call.ended",
  "accountId": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10",
  "call": {
    "id": "9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33",
    "endedAt": "2026-10-15T13:42:26Z",
    "durationS": 84,
    "intent": "booking",
    "topic": "Dental cleaning",
    "outcome": "booked",
    "sentiment": "positive",
    "confidence": 0.96,
    "summary": "New caller booked a dental cleaning for Tuesday at 10:00.",
    "recordingPath": "3c1d6c1e-5c55-4f3e-9d0b-6b1f6f2a7a10/9b2f7d4e-1a7c-4e0f-8f5e-2d6c4b9a1e33.mp3"
  }
}
Campo de callValores
endedAtISO 8601
durationSsegundos enteros
intentbooking reschedule cancel pricing insurance billing hours emergency general spam
topictexto libre
outcomebooked rescheduled cancelled info transferred voicemail missed spam
sentimentpositive neutral negative
confidence0–1
summarytexto libre
handoffReasontexto libre, cuando el resultado es transferred
recordingPath<accountId>/<callId>.mp3 en el bucket privado cw-recordings

Cómo se aplica

La ruta comprueba la firma y el esquema, y después llama a la función de Postgres cw_apply_voice_event(jsonb) con el rol de servicio. Solo el rol de servicio puede ejecutar esa función, y los usuarios con sesión iniciada no tienen permiso de inserción en las llamadas ni en los turnos, así que el webhook es la única vía por la que se crean los registros de llamadas.

Antes del lanzamiento

Consulte la entrada del servicio de voz en Conexiones para ver qué hay que verificar: la latencia, el manejo de interrupciones (barge-in) y la rotación del secreto.

On this page