Skip to main content

Referencia de la API v1

Referencia de integración REST + Webhooks (versión 2026-06-15)

v1 · 2026-06-15Beta

Guía rápida de integración de CRM

La vía más rápida para conectar un CRM a Heilo. La referencia completa de la API está más abajo.

Integre con un enfoque webhook-first: el evento call.completed es la fuente de datos principal (lleva el enlace de la grabación y la transcripción procesada). REST /calls es un complemento: introspección y lectura de metadatos seleccionados.

  1. Exponga un endpoint de webhook en su CRM o middleware (sin código: use Zapier/Make; vea la guía más abajo).
  2. Añada una suscripción en Heilo (Ajustes → Integraciones) para call.completed (opcionalmente también call.outbound.attempted).
  3. Reciba call.completed y verifique la firma (cabecera Heilo-Signature, HMAC; vea más abajo).
  4. Deduplique por event_id (cabecera heilo-event-id); data.call_id agrupa los eventos de la misma llamada.
  5. Mapee los campos a su CRM: busque/cree un contacto por teléfono, cree un lead/negocio y adjunte una actividad/nota (resumen + enlace de la grabación).

¿Sin código? La guía de conexión sin código (Zapier/Make) lo explica paso a paso.

Autenticación

La API pública usa tokens Bearer. Genere una clave API desde la tarjeta «Claves API» de la página de Integraciones y envíela en la cabecera:

Authorization: Bearer hk_live_AbC1MnPq...

Heilo tiene tres modos de autenticación:

  • Bearer (claves API hk_live_…): para la API pública. Sin CSRF, sin cookies.
  • Cookies de sesión: para la aplicación web (heilo.io). NO las use para la API pública.
  • HMAC-SHA256: para los Webhooks que Heilo envía a SU endpoint (usted verifica la cabecera de firma).
Permiso (scope)Significado
read.callsLectura de llamadas: GET /api/v1/calls, GET /api/v1/calls/:id
read.recordingsEmite un enlace al audio de una llamada mediante `GET /calls/{id}/recording-url`. Separado de `read.calls` porque el audio es un consentimiento distinto del de los metadatos: una clave existente no lo adquiere, se crea una nueva con acceso explícito a grabaciones.
write.calls, read.contacts, write.contacts, manage.webhooks, manage.api_keysReservados para los endpoints de API planificados; no los marque por adelantado.

El campo environment en la respuesta de /me tiene hoy siempre el valor live. Las claves de prueba están previstas.

URL base y versión

Todos los endpoints públicos están bajo /api/v1/. Producción:

https://www.heilo.io/api/v1

Versión actual

v1 · 2026-06-15

La fecha es el identificador de versión de la API (con fecha), no la fecha de hoy.

Status

Beta

La API de Heilo usa una versión con fecha. Solo los cambios incompatibles incrementan la versión principal (v1 → v2). Añadir campos o endpoints no rompe la compatibilidad.

Compatible con versiones anteriores: nuevos campos de respuesta, nuevos event_types, nuevos endpoints. Cambio incompatible = nueva versión principal (v2). La versión antigua se mantiene un mínimo de 12 meses tras el anuncio de v2.

Límites de tasa

Límites por hora por clave y por usuario (suma de todas las claves). Se reinician en la hora UTC. Cada solicitud cuenta, independientemente del estado de la respuesta.

Por clave

1000 req/h

Por cuenta (suma de las claves)

5000 req/h

Cuando se supera, devolvemos 429 con la cabecera Retry-After (segundos hasta el reinicio):

HTTP/1.1 429 Too Many Requests
Retry-After: 1842
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-06-03T13:00:00Z

Errores

Todos los errores devuelven una estructura JSON uniforme con error.code (estable) y error.message (legible por humanos, puede cambiar). Registre el code, no el message.

{
  "success": false,
  "error": { "code": "RATE_LIMITED", "message": "Per-key rate limit 1000/h exceeded" },
  "meta": { "timestamp": "2026-06-03T12:34:56Z" }
}
HTTPcodeSignificado
400BAD_REQUESTParámetros de consulta o de cuerpo mal formados (validación genérica)
401UNAUTHORIZEDToken Bearer ausente o no válido
402SUBSCRIPTION_INACTIVESuscripción inactiva: renueve la facturación para reactivar la clave
403FORBIDDENLa clave no tiene el scope requerido
404NOT_FOUNDEl recurso no existe o está fuera de la organización de la clave API.
422VALIDATION_ERRORUna regla de negocio rechazó la solicitud (p. ej. número de teléfono no válido, cuota)
429RATE_LIMITEDLímite por hora superado (consulte Retry-After)
500DATABASE_ERRORError de servidor / base de datos: es seguro reintentar con backoff
503MAINTENANCEAPI pública desactivada temporalmente (kill switch)

El código SUBSCRIPTION_INACTIVE aparece en dos situaciones: HTTP 402, la suscripción de Heilo ha caducado (pago), y HTTP 409, la suscripción del webhook está pausada (p. ej., con la acción de prueba); en ese caso, haga clic primero en «Volver a verificar».

Cada endpoint de /api/v1 requiere Bearer. El /me siguiente sirve para la introspección de la clave; la lectura de llamadas está en la sección «Llamadas». Al integrar un CRM, trate los webhooks como la fuente de datos principal: REST sirve para la introspección y para leer metadatos seleccionados.

GET

/api/v1/me

read.callsProbar

Introspección de la clave API: devuelve el id de la clave, user_id, scopes y el límite de tasa. Útil para la «prueba de conexión» de Zapier/Make.

Caso de uso: prueba de conexión de Zapier durante la configuración de una integración personalizada. Una respuesta 200 demuestra que la clave y la red funcionan.

Solicitud
curl https://www.heilo.io/api/v1/me \
  -H "Authorization: Bearer hk_live_AbC1MnPq..."
Respuesta
{
  "success": true,
  "data": {
    "api_key_id": "a1b2c3d4-5e6f-7081-92a3-b4c5d6e7f809",
    "user_id": "5f4e3d2c-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "organization_id": "7a8b9c0d-1e2f-4a3b-9c8d-7e6f5a4b3c2d",
    "scopes": ["read.calls", "manage.webhooks"],
    "rate_limit_per_hour": 1000,
    "environment": "live"
  },
  "meta": { "timestamp": "2026-06-03T12:34:56Z" }
}

Endpoints de lectura para llamadas. Requieren el scope read.calls.

GET

/api/v1/calls

read.callsProbar

Lista las llamadas de la organización. Paginación (page/limit≤100), filtros: direction, status, rango de fechas (dateFrom/dateTo). Devuelve has_more.

Parámetros de consulta

ParámetroTipoValores
pageintdesde 1 (por defecto 1)
limitint1–100 (por defecto 20)
directionenuminbound | outbound
statusenumnew | to_call | contacted | qualified
dateFrom / dateTostringfecha YYYY-MM-DD o ISO 8601, p. ej. 2026-06-03T12:34:56Z
phonestringel número del cliente de la llamada; los espacios, guiones y el + inicial son opcionales, p. ej. 600 100 200
Solicitud
curl "https://www.heilo.io/api/v1/calls?limit=20&direction=inbound" \
  -H "Authorization: Bearer hk_live_AbC1MnPq..."
Respuesta
{
  "success": true,
  "data": {
    "items": [
      {
        "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "direction": "inbound",
        "caller_phone": "+48600100200",
        "customer_phone_e164": "+48600100200",
        "customer_phone_national": "600 100 200",
        "company_phone_e164": "+48222630000",
        "app_url": "https://www.heilo.io/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "caller_name": "Jan Kowalski",
        "duration": 87,
        "crm_status": "new",
        "review_status": null,
        "outbound_lifecycle": null,
        "transcript_processed": { "caller_name": "Jan Kowalski", "summary": "...", "service_needed": "...", "followup_email": null },
        "created_at": "2026-06-03T12:34:56Z"
      }
    ],
    "has_more": false,
    "page": 1,
    "limit": 20
  },
  "meta": { "timestamp": "2026-06-03T12:34:56Z" }
}

Obtiene una sola llamada por id. 404 si no pertenece a la organización de la clave o si se eliminó.

Solicitud
curl https://www.heilo.io/api/v1/calls/<id> \
  -H "Authorization: Bearer hk_live_AbC1MnPq..."

Un enlace recién firmado al audio de la llamada, válido 15 minutos. Es un permiso de descarga con fecha límite, no una referencia duradera: guarda el `call_id` y vuelve a pedirlo, porque un enlace guardado en un campo del CRM deja de servir en menos de una hora. El enlace del webhook `call.recording.ready` está firmado siete días — este endpoint es la forma de llegar al audio después.

  • 200 — el enlace y el momento en que deja de funcionar.
  • 409 `RECORDING_NOT_READY` — la llamada es tuya y el audio aún no ha llegado. Vuelve a preguntar en breve.
  • 409 `RECORDING_WITHHELD` — la llamada está en la papelera o programada para borrarse. Ambas decisiones se pueden deshacer, así que sigue preguntando en lugar de dar la grabación por perdida.
  • 410 `RECORDING_DELETED` — el barrido de retención eliminó el audio y no se puede recuperar. Deja de preguntar y deja de encolar.
  • 410 `RECORDING_NOT_CAPTURED` — esta llamada saliente nunca se grabó y nunca existirá una grabación para ella: la puerta de captura la rechazó al iniciarse, se ejecutó en modo no_recording o terminó sin conversación. Deja de preguntar; no hay motivo para reintentarlo. Una llamada entrante cuya grabación se rechazó sigue respondiendo 409, porque todavía puede llegar una grabación.

La respuesta se sirve con `Cache-Control: no-store`, porque el cuerpo lleva una credencial. El permiso se vuelve a comprobar al descargar el archivo, no solo al emitir el enlace, así que una llamada borrada entretanto deja de ser legible de inmediato.

Solicitud
curl https://www.heilo.io/api/v1/calls/<id>/recording-url \
  -H "Authorization: Bearer hk_live_AbC1MnPq..."
Respuesta
{
  "success": true,
  "data": {
    "url": "https://www.heilo.io/api/v1/calls/<id>/recording.mp3?token=...&exp=...",
    "expires_at": "2026-09-15T12:15:00.000Z"
  }
}

Los endpoints de lista y detalle de llamadas no devuelven archivos de grabación. El enlace a la grabación llega en un webhook y, una vez caducado, en `GET /calls/{id}/recording-url`. Las grabaciones pueden borrarse conforme a las reglas de retención y del RGPD.

Qué trae REST y qué solo trae un webhook

Los dos canales no llevan lo mismo, y la diferencia decide cuál eliges. REST es duradero: puedes leer una llamada en cualquier momento. Un webhook es más rico, pero llega una vez y parte de lo que lleva caduca.

DatosRESTWebhook
Metadatos de la llamada: dirección, números, duración, fechasísí
Estado en el CRM y estado de revisiónsíno
Resumen, nombre de quien llama, necesidad del clientesísí
El resto del análisis: ciudad, dirección, puntuación del lead, fecha preferida, idiomanosí
La transcripción literalnosí
Enlace a la grabaciónsí, vía /calls/{id}/recording-urlsí, válido 7 días

De ahí la regla práctica: toma el análisis completo del webhook en el momento en que llega, porque REST nunca lo llevó. La grabación es lo único que ahora se puede recuperar más tarde — pide a /calls/{id}/recording-url un enlace nuevo de 15 minutos en lugar de guardar el de siete días de la carga útil.

Especificación y una petición real

Las mismas tres operaciones, escritas para máquinas: OpenAPI 3.1. Impórtalo en Postman o Insomnia, o genera un cliente en tu lenguaje.

Descargar OpenAPI 3.1

Especificación y una petición real

Envía una petición desde aquí y comprueba tu clave antes de escribir código.

Esto llama a la API real con tu clave real y devuelve tus propios datos. La clave se queda en esta pestaña — nunca la guardamos.

Webhooks salientes

Heilo envía eventos JSON firmados a tu endpoint. Crea suscripciones en la tarjeta Suscripciones de webhooks. La activación usa un handshake separado sin firma, webhook.subscription.verify, que nunca debe crear registros de negocio.

POST <your URL>
content-type: application/json
heilo-signature: t=1717423396,v1=4f3a...
heilo-event-id: 1bf3a5e2-...
heilo-event-type: call.completed

{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-...",
  "event_type": "call.completed",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-06-03T12:34:56Z",
  "data": { /* see the setup guide for the full schema */ }
}

Política de reintentos y pausa:

  • Los errores transitorios permiten hasta 5 intentos en total: después de 2 min, 5 min, 30 min y 2 h, con variación aleatoria (jitter) en los reintentos posteriores. Tras el quinto fallo, la entrega termina como dead-letter.
  • Los errores permanentes (HTTP 401/403/422) pausan la suscripción de inmediato, sin reintentos.
  • 50 fallos transitorios consecutivos, o 2 HTTP 410 Gone consecutivos (p. ej. un escenario de Make eliminado), también pausan la suscripción.
  • Reanude una suscripción pausada con «Volver a verificar»: un nuevo handshake reactiva la cola.

Si una suscripción se pausa automáticamente, enviamos un correo a la dirección del propietario de la cuenta. Puede reenviar los eventos en dead-letter con el botón «Reenviar» del Registro de entregas, una vez que la suscripción supere la nueva verificación.

Una respuesta 2xx confirma la recepción HTTP, no el fin de una automatización ni la escritura en el CRM. Las entregas pueden repetirse o llegar desordenadas. Heilo reconcilia lagunas recientes dentro de una ventana limitada, no todo el historial. Revisa por separado las entregas y el CRM.

Modos de verificación

El handshake sin firma contiene solo event_type = webhook.subscription.verify y challenge. Responde únicamente para confirmar el endpoint; no proceses datos de negocio. Ambos modos usan esta petición:

{
  "event_type": "webhook.subscription.verify",
  "challenge": "example-verification-token"
}

Modo permissive (predeterminado)

Cualquier respuesta 2xx activa la suscripción; se ignora su contenido. Es el modo predeterminado para un receptor no-code.

Modo strict (opcional)

Responde con 2xx y JSON con exactamente el valor challenge recibido de Heilo:

{"challenge":"<echo of the challenge field from Heilo's POST>"}

Elige el modo al crear la suscripción. Cambiarlo exige eliminarla y crearla de nuevo. Las rutas de gestión requieren una sesión autenticada y protección CSRF; no están disponibles con una clave API.

Límites: máximo 20 suscripciones activas por cuenta (configurable por variable de entorno). Una suscripción se pausa automáticamente tras 50 fallos transitorios consecutivos o 2 HTTP 410 consecutivos, e inmediatamente con HTTP 401/403/422.

Verificación HMAC (signing_secret)

Los eventos firmados, incluido el webhook.test manual, usan Heilo-Signature con HMAC-SHA256 sobre timestamp + punto + bytes originales de la petición. Verifica la firma antes de analizar el JSON de negocio. Solo el handshake de verificación no lleva firma.

signed_string = "<unix_timestamp>.<raw_request_body>"
signature     = HMAC-SHA256(signing_secret, signed_string).hex()
header        = "t=<unix_timestamp>,v1=<signature>"
import { createHmac, timingSafeEqual } from 'node:crypto';

// Pass the exact received Buffer (e.g. Express raw parser), never JSON.stringify(req.body).
// signingSecrets: every secret you currently trust, tried in constant time each.
// During a secret rotation pass [newSecret, oldSecret]; remove the old one once
// Heilo's grace window ends. Heilo itself always sends exactly ONE v1 signature —
// this list is what lets your receiver accept it under either secret while both
// are live, never a second signature to parse.
function verifyHeiloSignature(rawBody, header, signingSecrets) {
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header || '');
  if (!match) return false;
  const timestamp = Number(match[1]);
  const now = Math.floor(Date.now() / 1000);
  // Receiver policy: at most 10 minutes old or 5 minutes ahead (clock skew).
  if (!Number.isSafeInteger(timestamp) || now - timestamp > 600 || timestamp - now > 300) return false;
  // One string is accepted too. Empty entries are dropped: an HMAC key of ''
  // is valid, so a blanked env var (OLD_SECRET=) would let anyone sign events.
  const secrets = [].concat(signingSecrets).filter((s) => typeof s === 'string' && s.length > 0);
  if (secrets.length === 0) return false;
  const actual = Buffer.from(match[2], 'hex');
  return secrets.some((signingSecret) => {
    const expected = createHmac('sha256', signingSecret)
      .update(match[1] + '.')
      .update(rawBody)
      .digest();
    return actual.length === expected.length && timingSafeEqual(actual, expected);
  });
}

function receiveHeiloWebhook(rawBody, signatureHeader, signingSecrets) {
  if (!signatureHeader) {
    // The ONLY unsigned exception confirms the endpoint; never enqueue it for CRM writes.
    const probe = JSON.parse(rawBody.toString('utf8'));
    if (probe && typeof probe === 'object' && !Array.isArray(probe)
      && Object.keys(probe).length === 2
      && probe.event_type === 'webhook.subscription.verify'
      && typeof probe.challenge === 'string') {
      return { kind: 'verification', response: { challenge: probe.challenge } };
    }
    throw new Error('Missing Heilo-Signature');
  }
  if (!verifyHeiloSignature(rawBody, signatureHeader, signingSecrets)) throw new Error('Invalid Heilo-Signature');
  return { kind: 'event', event: JSON.parse(rawBody.toString('utf8')) };
}

// HTTP adapter: limit body size; map verification to 200 JSON response.
// Signature errors -> 401 (pauses delivery; fix the secret, then Reverify). JSON errors -> 400.
// For kind=event: validate the envelope and chosen event_type, ignore webhook.test
// in business flows, then durably deduplicate/enqueue by event_id BEFORE returning 2xx.
// Enqueue failure: return 503. An HTTP acknowledgement is not proof of a CRM write.

Estos ejemplos aceptan firmas de hasta 600 segundos (10 minutos) de antigüedad y 300 segundos (5 minutos) en el futuro, igual que el verificador actual de Heilo. Sincroniza los relojes. El control de tiempo limita la ventana de repetición; sigue siendo necesario deduplicar por event_id.

El secreto de firma se muestra solo una vez, al crear la suscripción. ¿Lo perdió o sospecha una filtración? Abra la suscripción en Configuración → Integraciones → Webhooks y rótelo allí — vea «Rotación del secreto» más abajo. No hace falta eliminar la suscripción y volver a crearla.

Rotación del secreto

Heilo siempre envía exactamente una firma v1, nunca dos a la vez. La rotación funciona por orden de instalación, no enseñando a su receptor a analizar varias firmas.

  1. Prepare un secreto nuevo en la configuración de la suscripción. Se muestra una sola vez y espera hasta 24 horas para activarse.
  2. Instale el secreto nuevo JUNTO AL actual — su receptor debe aceptar una firma hecha con cualquiera de los dos.
  3. Opcionalmente envíe una prueba firmada con el secreto nuevo. Lleva la cabecera Heilo-Secret-Rotation-Id y un cuerpo webhook.test normal; una respuesta 2xx no prueba que su receptor comprobó la firma — confírmelo en su propio registro.
  4. Active la rotación.
  5. Mantenga instalado el secreto anterior 1 hora después de la activación y luego elimínelo.

Heilo no cambia nada en su receptor. Durante ese periodo es su receptor el que debe aceptar una firma hecha con cualquiera de los dos secretos.

Emergencia: el secreto se filtró

Una rotación de emergencia pausa el envío de inmediato. Instale SOLO el secreto nuevo — trate el anterior como comprometido — y luego actívelo; no hay ventana de gracia. Reanude el envío después con «Volver a verificar». Las entregas que ya estaban en cola al pausar se omiten; reenvíelas desde el registro de entregas.

¿Perdió el secreto sin filtración? Una rotación estándar también funciona — no hace falta eliminar la suscripción.

Tipos de evento

Elija los event_types al crear una suscripción. Cada evento tiene un event_id único (UUID v5) y se deduplica por suscripción.

webhook.test se envía con el botón manual Test y tiene data._test = true. Úsalo para mapear campos y después exclúyelo del procesamiento de negocio. El handshake de activación separado es webhook.subscription.verify.

event_typeDescripción
call.completedLlamada completada, transcripción lista
call.outbound.attemptedIntento de llamada saliente en estado final (conectada o fallida)
call.recording.readyArchivo de grabación disponible para descargar
call.transcribedTranscripción lista (separada de call.completed)
call.failedLlamada fallida (ocupado/sin respuesta/error)
call.outbound.lifecycle_repairedCorrección del ciclo de vida saliente: estado de la llamada reparado
call.deletion_scheduledLlamada programada para eliminación (RGPD art. 17, retención)
call.recording.deletedGrabación eliminada (RGPD)
call.followup_email.draftedBorrador de correo de seguimiento listo
call.tracker.matchedTracker coincidió
contact.createdNuevo contacto creado
contact.updatedContacto actualizado

A continuación, el objeto data de cada evento. Los nombres de campo, tipos y valores enumerados forman parte del contrato de la API y no cambian de significado dentro de v1; con el tiempo pueden añadirse nuevos campos opcionales.

call.completed

Se envía tras procesar la grabación y la transcripción de una llamada completada. La fuente de datos principal para un CRM: payload completo (incluidos recording_url y la transcripción procesada).

{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.completed",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "direction": "inbound",
    "caller_phone": "+447700900123",
    "customer_phone_e164": "+447700900123",
    "customer_phone_national": "07700 900123",
    "company_phone_e164": "+48222630000",
    "call_created_at": "2026-09-08T10:00:00Z",
    "app_url": "https://www.heilo.io/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "duration": 87,
    "recording_url": "https://www.heilo.io/api/v1/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6/recording.mp3?token=EXAMPLE&exp=1789466400",
    "transcript_processed": {
      "caller_name": "Alicja Testowa",
      "counterparty_name": null,
      "caller_location": "Warszawa, Mokotów",
      "client_address": "ul. Przykładowa 10",
      "client_city": "Warszawa",
      "client_country": "Poland",
      "service_needed": "Tynki gipsowe w mieszkaniu 65m²",
      "services_match": true,
      "preferred_date": "w przyszłym tygodniu",
      "additional_details": "Klient wspomniał o terminie do końca czerwca. Druga rozmowa po wycenie.",
      "lead_score": 7,
      "summary": "Prośba o wycenę tynków gipsowych w mieszkaniu 65 m² na Mokotowie.",
      "subject": "Wycena tynków — Warszawa, 65 m²",
      "detected_language": "pl"
    },
    "transcript_original": "Proszę o ofertę w przyszłym tygodniu."
  }
}
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
direction'inbound' | 'outbound'Dirección de la llamada.
caller_phonestring | nullNúmero guardado del llamante; null si no está disponible en call.completed.
customer_phone_e164string | nullNúmero del cliente para buscar el contacto en el CRM; null si no está disponible en call.completed.
customer_phone_nationalstring | nullEl mismo número del cliente en formato nacional, listo para pegar en un campo de teléfono del CRM; null cuando el valor guardado no es un número utilizable, por ejemplo con un identificador oculto.
company_phone_e164string | nullEl número por el que entró o salió esta llamada. Normalmente tu número de Heilo; en una llamada desviada es tu propio número, el que la desvió. Sirve para encaminar notas cuando tienes más de un número; null si no se registró.
call_created_atstring (ISO 8601) | nullCuándo se registró la llamada en Heilo (ISO 8601). No es lo mismo que el created_at del sobre, que es el momento del envío y puede ser días posterior en un evento reparado.
app_urlstring | nullEnlace permanente a la llamada en Heilo. A diferencia de recording_url no caduca, así que puede guardarse en un registro del CRM. Abrirlo requiere iniciar sesión en Heilo.
durationnumberDuración de la grabación en segundos.
recording_urlstring | nullEnlace estable a la grabación; null cuando la grabación aún no estaba guardada al procesar.
transcript_processedobjectAnálisis procesado de la llamada; consulte la referencia de campos de transcript_processed en esta sección.
transcript_originalstring | nullTranscripción literal sin procesar; null cuando no está disponible.

La entrega es al menos una vez y no se garantiza el orden: persista de forma idempotente. Clave de deduplicación: event_id. data.call_id vincula los eventos de la misma llamada (p. ej. call.completed después de call.recording.ready).

call.outbound.attemptedSe envía cuando un intento de llamada saliente alcanza un estado final, incluidos los fallos. completed significa que la llamada se estableció y terminó con normalidad; la grabación y la transcripción llegan como eventos separados.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
agent_user_idstring (uuid)Identificador del usuario de Heilo que realizó la llamada.
customer_phonestringNúmero del cliente marcado.
customer_phone_e164stringNúmero del cliente para buscar el contacto en el CRM; null si no está disponible en call.completed.
outbound_lifecycle'completed' | 'agent_no_answer' | 'customer_no_answer' | 'failed_to_initiate'Estado final que desencadenó el evento; completed significa que la llamada se estableció y terminó con normalidad.
durationnumber | nullDuración de la llamada en segundos; null cuando la llamada falló al iniciarse o la duración aún no se conoce.
attempted_atstring (ISO 8601)Momento de emisión del evento (ISO 8601).
has_recordingbooleantrue solo cuando outbound_lifecycle es completed y la llamada no estaba en modo no_recording; la grabación llega entonces como call.recording.ready.
call.outbound.attempted
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.outbound.attempted",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "agent_user_id": "2ce127a0-c621-483d-b576-e68f69d95e84",
    "customer_phone": "+447700900123",
    "customer_phone_e164": "+447700900123",
    "outbound_lifecycle": "completed",
    "duration": 95,
    "attempted_at": "2026-09-08T10:00:00Z",
    "has_recording": true
  }
}
call.recording.readySe envía cuando el archivo de la grabación está disponible. Úselo para obtener o archivar el audio.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
recording_urlstring | nullEnlace estable a la grabación; en casos raros null, cuando no se pudo generar el enlace.
durationnumber | nullDuración en segundos; null cuando aún no se conoce.
call.recording.ready
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.recording.ready",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "recording_url": "https://www.heilo.io/api/v1/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6/recording.mp3?token=EXAMPLE&exp=1789466400",
    "duration": 87
  }
}
call.transcribedSe envía en el mismo procesamiento que call.completed: contiene solo la transcripción, sin metadatos de la llamada ni enlace a la grabación.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
transcript_originalstring | nullTranscripción literal sin procesar; null cuando no está disponible.
transcript_processedobjectAnálisis procesado de la llamada; consulte la referencia de campos de transcript_processed en esta sección.
call.transcribed
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.transcribed",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "transcript_original": "Proszę o ofertę w przyszłym tygodniu.",
    "transcript_processed": {
      "caller_name": "Alicja Testowa",
      "counterparty_name": null,
      "caller_location": "Warszawa, Mokotów",
      "client_address": "ul. Przykładowa 10",
      "client_city": "Warszawa",
      "client_country": "Poland",
      "service_needed": "Tynki gipsowe w mieszkaniu 65m²",
      "services_match": true,
      "preferred_date": "w przyszłym tygodniu",
      "additional_details": "Klient wspomniał o terminie do końca czerwca. Druga rozmowa po wycenie.",
      "lead_score": 7,
      "summary": "Prośba o wycenę tynków gipsowych w mieszkaniu 65 m² na Mokotowie.",
      "subject": "Wycena tynków — Warszawa, 65 m²",
      "detected_language": "pl"
    }
  }
}
call.failedSe envía cuando una llamada saliente no se completó (sin respuesta, ocupado, error al iniciar). Solo afecta a llamadas salientes. Normalmente no conviene crear un lead; registre un intento de contacto.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
outbound_lifecycle'agent_no_answer' | 'customer_no_answer' | 'failed_to_initiate'Qué fase de la llamada saliente falló.
failure_reasonstring | nullCódigo técnico del motivo (p. ej. customer_busy, agent_no_confirmation); puede ser null.
call.failed
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.failed",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "outbound_lifecycle": "customer_no_answer",
    "failure_reason": "customer_busy"
  }
}
call.outbound.lifecycle_repairedSe envía cuando Heilo corrige retroactivamente el estado de una llamada saliente (una confirmación tardía del operador demostró que la llamada sí se estableció). Actualice el estado de la llamada en su sistema.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
previous_lifecycle'agent_only'Estado antes de la corrección; actualmente siempre agent_only.
new_lifecycle'completed'Estado tras la corrección; actualmente siempre completed.
repaired_atstring (ISO 8601)Momento de la corrección (ISO 8601).
call.outbound.lifecycle_repaired
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.outbound.lifecycle_repaired",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "previous_lifecycle": "agent_only",
    "new_lifecycle": "completed",
    "repaired_at": "2026-09-08T10:00:00Z"
  }
}
call.deletion_scheduledRGPD art. 17: la llamada está programada para su eliminación. Su CRM debería dejar de usar la grabación y prepararse para eliminar los datos. Solo se le informa de una llamada si Heilo tiene constancia de que su contenido pudo llegar a su endpoint. Suscribirse únicamente a los eventos de borrado no le convierte en destinatario: no hay nada que borrar si nunca recibió nada.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
pending_deletion_atstring (ISO 8601)Cuándo se eliminarán definitivamente los datos (ISO 8601).
reason'consent_not_asked' | 'consent_withdrawn' | 'retention_expired' | 'user_erasure'Motivo de la eliminación programada.
call.deletion_scheduled
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.deletion_scheduled",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "pending_deletion_at": "2026-09-09T10:00:00Z",
    "reason": "user_erasure"
  }
}
call.recording.deletedRGPD: la grabación se eliminó; recording_url devuelve 410. Elimine o desactive el enlace de la grabación por su parte. Solo se le informa de una llamada si Heilo tiene constancia de que su contenido pudo llegar a su endpoint, por lo que esto no es un registro completo de todos los borrados de su organización. Un 2xx por su parte significa que la instrucción llegó, nunca que la copia haya desaparecido.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
reasonstring | nullMotivo registrado al programar la eliminación; puede ser null.
recording_sidstring | nullIdentificador de la grabación en Twilio; null cuando no se pudo determinar.
deletion_kind'hard_deleted' | 'twilio_404'hard_deleted = eliminada por Heilo; twilio_404 = el archivo ya no existía en Twilio.
deleted_atstring (ISO 8601)Momento de la eliminación de la grabación (ISO 8601).
call.recording.deleted
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.recording.deleted",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "reason": "user_erasure",
    "recording_sid": null,
    "deletion_kind": "twilio_404",
    "deleted_at": "2026-09-08T10:00:00Z"
  }
}
call.followup_email.draftedSe envía después de procesar una llamada bidireccional cuando Heilo ha redactado un borrador de correo de seguimiento a partir de lo acordado. No se envía para el buzón de voz ni cuando no se pudo redactar. Heilo NUNCA envía este correo a la persona que llamó — el borrador es para que tu comercial lo revise y lo envíe.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
subjectstringAsunto del correo, listo para pegar.
bodystringCuerpo del correo en texto plano, SIN firma — el comercial añade la suya al enviarlo.
languagestringIdioma del borrador: el idioma detectado de la llamada o, en su defecto, el idioma de la cuenta configurado en Heilo (no un ajuste por número).
call.followup_email.drafted
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.followup_email.drafted",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "subject": "Oferta po rozmowie",
    "body": "Dzień dobry, przesyłam ustalenia naszej rozmowy.",
    "language": "pl"
  }
}
call.tracker.matchedSe envía cuando al menos un tracker de palabras clave coincide con la transcripción procesada. Incluye términos y fragmentos; si faltan fragmentos, la lista está vacía.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
matchesobject[]Trackers encontrados en esta llamada.
matches[].tracker_idstring (uuid)Identificador del tracker configurado.
matches[].namestringNombre del tracker.
matches[].matched_termsstring[]Términos encontrados.
matches[].excerptsstring[]Fragmentos de transcripción; puede estar vacío.
call.tracker.matched
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "call.tracker.matched",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "matches": [
      {
        "tracker_id": "cd794362-6117-44d1-b487-c5e7e1ea9082",
        "name": "Oferta",
        "matched_terms": [
          "ofertę"
        ],
        "excerpts": [
          "Proszę o ofertę w przyszłym tygodniu."
        ]
      }
    ]
  }
}
contact.createdSe envía al crear un contacto nuevo en Heilo. data.contact es una instantánea completa del contacto nuevo; no incluye notas ni etiquetas.
CampoTipoDescripción
contactobjectInstantánea completa del contacto nuevo (campos abajo).
contact.idstring (uuid)Identificador del contacto en Heilo.
contact.phonestringNúmero de teléfono del contacto.
contact.first_namestring | nullnull si no se indicó.
contact.last_namestring | nullnull si no se indicó.
contact.emailstring | nullnull si no se indicó.
contact.companystring | nullnull si no se indicó.
contact.created
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "contact.created",
  "resource_id": "86c68698-d54c-43e7-bbbc-328499b98d12",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "contact": {
      "id": "86c68698-d54c-43e7-bbbc-328499b98d12",
      "phone": "+447700900123",
      "first_name": "Alicja",
      "last_name": "Testowa",
      "email": "alicja@example.com",
      "company": null
    }
  }
}
contact.updatedSe envía al editar un contacto. A diferencia de contact.created, no es una instantánea: data.diff contiene solo los campos modificados.
CampoTipoDescripción
contact_idstring (uuid)Identificador del contacto actualizado.
diffobject (partial)Solo los campos modificados; las claves ausentes de diff no cambiaron.
contact.updated
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "contact.updated",
  "resource_id": "86c68698-d54c-43e7-bbbc-328499b98d12",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "contact_id": "86c68698-d54c-43e7-bbbc-328499b98d12",
    "diff": {
      "company": "Example",
      "notes": "Oddzwonić",
      "tags": [
        "oferta"
      ]
    }
  }
}

Claves posibles en diff: first_name, last_name, email, phone, company, notes, tags

webhook.testSe envía solo al pulsar Test en una suscripción. resource_id identifica la suscripción, no una llamada. data contiene datos sintéticos de call.completed y los tres marcadores descritos abajo. No demuestra que exista audio real.
CampoTipoDescripción
call_idstring (uuid)ID de llamada en Heilo. Coincide con resource_id en eventos de llamadas de negocio; en webhook.test es un ID sintético.
direction'inbound' | 'outbound'Dirección de la llamada.
caller_phonestring | nullNúmero guardado del llamante; null si no está disponible en call.completed.
customer_phone_e164string | nullNúmero del cliente para buscar el contacto en el CRM; null si no está disponible en call.completed.
customer_phone_nationalstring | nullEl mismo número del cliente en formato nacional, listo para pegar en un campo de teléfono del CRM; null cuando el valor guardado no es un número utilizable, por ejemplo con un identificador oculto.
company_phone_e164string | nullEl número por el que entró o salió esta llamada. Normalmente tu número de Heilo; en una llamada desviada es tu propio número, el que la desvió. Sirve para encaminar notas cuando tienes más de un número; null si no se registró.
call_created_atstring (ISO 8601) | nullCuándo se registró la llamada en Heilo (ISO 8601). No es lo mismo que el created_at del sobre, que es el momento del envío y puede ser días posterior en un evento reparado.
app_urlstring | nullEnlace permanente a la llamada en Heilo. A diferencia de recording_url no caduca, así que puede guardarse en un registro del CRM. Abrirlo requiere iniciar sesión en Heilo.
durationnumberDuración de la grabación en segundos.
recording_urlstring | nullEnlace estable a la grabación; null cuando la grabación aún no estaba guardada al procesar.
transcript_processedobjectAnálisis procesado de la llamada; consulte la referencia de campos de transcript_processed en esta sección.
transcript_originalstring | nullTranscripción literal sin procesar; null cuando no está disponible.
_testtrueSiempre true; distingue el mensaje de prueba de los eventos reales.
_messagestringAviso legible de que se trata de una prueba.
_sent_atstring (ISO 8601)Momento del envío de la prueba (ISO 8601).
webhook.test
{
  "api_version": "2026-06-15",
  "event_id": "1bf3a5e2-4f82-4335-988e-4e044fa648d4",
  "event_type": "webhook.test",
  "resource_id": "c2d38cc8-bc1d-4336-91ea-51b8a549a882",
  "created_at": "2026-09-08T10:00:00Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "direction": "inbound",
    "caller_phone": "+447700900123",
    "customer_phone_e164": "+447700900123",
    "customer_phone_national": "07700 900123",
    "company_phone_e164": "+48222630000",
    "call_created_at": "2026-09-08T10:00:00Z",
    "app_url": "https://www.heilo.io/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "duration": 87,
    "recording_url": "https://www.heilo.io/api/v1/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6/recording.mp3?token=EXAMPLE&exp=1789466400",
    "transcript_processed": {
      "caller_name": "Alicja Testowa",
      "counterparty_name": null,
      "caller_location": "Warszawa, Mokotów",
      "client_address": "ul. Przykładowa 10",
      "client_city": "Warszawa",
      "client_country": "Poland",
      "service_needed": "Tynki gipsowe w mieszkaniu 65m²",
      "services_match": true,
      "preferred_date": "w przyszłym tygodniu",
      "additional_details": "Klient wspomniał o terminie do końca czerwca. Druga rozmowa po wycenie.",
      "lead_score": 7,
      "summary": "Prośba o wycenę tynków gipsowych w mieszkaniu 65 m² na Mokotowie.",
      "subject": "Wycena tynków — Warszawa, 65 m²",
      "detected_language": "pl"
    },
    "transcript_original": "Proszę o ofertę w przyszłym tygodniu.",
    "_test": true,
    "_message": "Synthetic webhook test; do not create CRM records.",
    "_sent_at": "2026-09-08T10:00:00Z"
  }
}

transcript_processed: referencia de campos

El subconjunto estable en el que puede confiar al mapear a un CRM. Todos los campos son opcionales: valen null cuando la llamada no contenía esa información.

CampoTipoDescripción
caller_namestring | nullNombre del llamante, si lo dio.
summarystring | nullResumen breve de la llamada.
subjectstring | nullTítulo de una línea de la llamada (hasta 80 caracteres).
service_neededstring | nullQué pidió el llamante.
services_matchboolean | nullSi la petición encaja con los servicios que usted ofrece.
lead_scorenumber | null (1–10)Estimación de la calidad del lead, de 1 a 10.
preferred_datestring | nullFecha u hora mencionada por el llamante, si la hubo.
client_citystring | nullCiudad, si se mencionó.
client_addressstring | nullDirección, si se mencionó.
additional_detailsstring | nullContexto adicional de la llamada.

Campos que pueden aparecer

CampoTipoDescripción
caller_locationstring | nullReferencia geográfica detectada en la conversación.
client_countrystring | nullPaís, si se mencionó.
counterparty_namestring | nullNombre de la otra parte — solo en llamadas salientes y en llamadas en vivo que Heilo escucha en silencio (modo oyente).
detected_languagestringCódigo de idioma de la conversación (p. ej. pl); la clave puede faltar por completo.
proposal_itemsobject[] | nullAcciones y decisiones sugeridas extraídas de la llamada.

El análisis puede incluir campos adicionales: trate los campos desconocidos como opcionales y no dé por hecha su presencia.

Roadmap (v1.1+)

Endpoints previstos para próximas versiones v1.X. No es un compromiso firme: la dirección depende de sus comentarios.

  • GET /contacts — listar contactos
  • POST /contacts — crear contacto (sincronizar desde el CRM hacia Heilo)
  • POST /calls — iniciar una llamada saliente a través de su número Heilo; se graba, se transcribe y se entrega a su CRM como cualquier otra llamada

¿Necesita un endpoint? Escriba a support@heilo.io con su caso de uso: priorizamos el roadmap según la demanda real.

Roadmap (v1.1+)

¿Necesita un endpoint? Escriba a support@heilo.io con su caso de uso: priorizamos el roadmap según la demanda real.

Gestione claves y webhooks en el panel

Tras iniciar sesión podrá generar claves API, añadir suscripciones de webhooks y consultar el historial de envíos.