Skip to main content

Referencia de la API v1

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

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

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

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

Endpoints

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

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.

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" }
}

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.

Llamadas

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

GET/api/v1/calls

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

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",
        "caller_name": "Jan Kowalski",
        "duration": 87,
        "crm_status": "new",
        "review_status": null,
        "outbound_lifecycle": null,
        "transcript_processed": { "caller_name": "Jan Kowalski", "summary": "...", "service_needed": "..." },
        "created_at": "2026-06-03T12:34:56Z"
      }
    ],
    "has_more": false,
    "page": 1,
    "limit": 20
  },
  "meta": { "timestamp": "2026-06-03T12:34:56Z" }
}
GET/api/v1/calls/:id

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..."

Los archivos de grabación NO se exponen a través de la API: se borran según la retención legal (RGPD). La API devuelve los metadatos de la llamada y el texto de la transcripción.

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_FOUNDRecurso no encontrado (o pertenece a otro usuario)
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».

Webhooks salientes

Heilo envía un POST con JSON a su endpoint tras cada evento. Las suscripciones se crean desde la tarjeta «Suscripciones de Webhook»: requiere un handshake en la activación. Cada solicitud incluye una firma HMAC que debe verificar:

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 (HTTP 408/429/5xx, red) se reintentan con backoff exponencial (2 min, 5 min, 30 min, 2 h) y luego pasan a dead-letter tras 5 intentos.
  • 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.

Modos de verificación

Heilo admite dos modelos de activación para las suscripciones de webhook. El predeterminado (permisivo) encaja con Zapier / Make / los endpoints típicos de CRM. El estricto coincide con el modelo de la Slack Events API: útil para servidores personalizados que pueden devolver el challenge de forma síncrona.

Modo permissive (predeterminado)

Heilo envía un POST de webhook.subscription.verify a su URL. Cualquier respuesta 2xx activa la suscripción. El cuerpo de la respuesta se ignora. Esto coincide con el modelo de Stripe / GitHub / Twilio.

Modo strict (opcional)

Heilo envía un POST de webhook.subscription.verify con un campo challenge. Su endpoint DEBE responder con 2xx y un cuerpo JSON:

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

Usted elige el modo al crear la suscripción (verification_mode en POST /api/v1/webhook-subscriptions, permisivo por defecto). Cambiar el modo tras la creación requiere eliminar y volver a crear: es intencionado, ya que cambia la semántica del contrato.

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)

Formato compatible con Stripe. signed_string = "<unix_ts>.<raw_body>" (separados por un punto). Verifique siempre el cuerpo RAW de la solicitud (antes de analizar el JSON): cualquier normalización cambia la 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 'crypto';

// rawBody MUST be the exact received bytes — do NOT re-serialize the JSON.
function verifyHeiloSignature(rawBody, header, signingSecret, toleranceSec = 300) {
  if (!header) return false;
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=').map((s) => s.trim()))
  );
  const t = Number(parts.t);
  const sig = parts.v1;
  if (!Number.isFinite(t) || !sig) return false;

  // Asymmetric tolerance: reject old replays; allow only small clock skew ahead.
  const now = Math.floor(Date.now() / 1000);
  if (now - t > toleranceSec || t - now > 60) return false;

  const expected = createHmac('sha256', signingSecret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(sig, 'utf8');
  // timingSafeEqual THROWS on length mismatch — length-check first.
  return a.length === b.length && timingSafeEqual(a, b);
}

La tolerancia predeterminada de 300 s (5 min) evita los ataques de repetición. Los relojes del servidor deben estar sincronizados por NTP.

El secreto de firma se muestra solo una vez, al crear la suscripción. Si lo pierde o sospecha una filtración: elimine la suscripción y créela de nuevo con la misma URL (la dirección en Zapier/Make no cambia). La rotación del secreto sin eliminar está prevista.

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.

El primer evento que recibirá es webhook.test, un mensaje de prueba con data._test = true. Úselo para mapear los campos o fíltrelo.

event_typeDescripción
call.completedLlamada completada, transcripción lista
call.outbound.attemptedMarcación saliente intentada (antes de conectar)
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)
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-...",        // same value as heilo-event-id header
  "event_type": "call.completed",
  "resource_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-06-03T12:34:56Z",
  "data": {
    "call_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "direction": "inbound",
    "caller_phone": "+48600100200",
    "customer_phone_e164": "+48600100200",
    "duration": 87,
    "recording_url": "https://www.heilo.io/api/v1/calls/.../recording.mp3?token=...&exp=...",
    "transcript_processed": { "caller_name": "Jan Kowalski", "summary": "...", "service_needed": "..." },
    "transcript_original": "..."
  }
}
CampoTipoDescripción
call_idstring (uuid)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
direction'inbound' | 'outbound'Dirección de la llamada.
caller_phonestringNúmero de teléfono del interlocutor, tal como está guardado en la llamada.
customer_phone_e164stringEl mismo número de nuevo, un alias para facilitar el mapeo de campos.
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).

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 llamadas salientes y modo conversación.
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.

call.outbound.attempted

Se 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)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
agent_user_idstring (uuid)Identificador del usuario de Heilo que realizó la llamada.
customer_phonestringNúmero del cliente marcado.
customer_phone_e164stringEl mismo número de nuevo, un alias para facilitar el mapeo de campos.
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; la grabación llega entonces como call.recording.ready.

call.recording.ready

Se envía cuando el archivo de la grabación está disponible. Úselo para obtener o archivar el audio.

CampoTipoDescripción
call_idstring (uuid)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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.transcribed

Se 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)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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.failed

Se 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)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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.outbound.lifecycle_repaired

Se 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)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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.deletion_scheduled

RGPD 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.

CampoTipoDescripción
call_idstring (uuid)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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.recording.deleted

RGPD: la grabación se eliminó; recording_url devuelve 410. Elimine o desactive el enlace de la grabación por su parte.

CampoTipoDescripción
call_idstring (uuid)Identificador de la llamada en Heilo, el mismo valor que resource_id; vincula todos los eventos de una llamada.
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).

contact.created

Se 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.updated

Se 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.

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

webhook.test

El primer mensaje tras crear una suscripción y en cada prueba manual. resource_id es el identificador de la suscripción (no de una llamada); el resto de campos de data refleja el ejemplo de call.completed con valores de muestra.

CampoTipoDescripción
_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).
Versionado y roadmap

Versionado

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.

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

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.

Roadmap (v1.1+)

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

  • GET /contactslistar contactos
  • POST /contactscrear contacto (sincronizar desde el CRM hacia Heilo)

¿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.