Saltar al contenido
Menú de la documentación

Conecta tu sistema por webhook

Si tu CRM no es GoHighLevel ni Zoho — o si los leads viven en vuestra propia base de datos, en un formulario o en una plataforma de automatización — la conexión Webhook os deja hablar con Calligence por HTTP: nos enviáis un lead con una petición y os enviamos el resultado de cada llamada a vuestro endpoint, firmado. Esta guía es técnica: está escrita para quien va a programar las dos puntas.

Qué hace esta integración

#

Una conexión de webhook tiene dos mitades independientes. Podéis activar las dos, o solo una:

Entrada · vosotros → Calligence

Hacéis POST de un lead por petición a la URL de una campaña. El lead entra en esa campaña y el agente de IA le llama dentro de su ventana horaria.

Salida · Calligence → vosotros

Hacemos POST a vuestro endpoint un evento JSON firmado por cada hecho relevante: el resumen de cada llamada, el resultado final del lead y la cita acordada.

  • Solo entrada: nos mandáis leads y consultáis el resultado en Calligence. No configuráis endpoint.
  • Solo salida: cargáis los leads a mano en la campaña (subiendo un CSV) con una columna de identificador (external_id), y nosotros os notificamos. Un lead sin identificador no genera eventos.
  • Las dos: el circuito completo, que es el caso habitual.

A diferencia de las conexiones con un CRM (GoHighLevel, Zoho CRM), aquí no leemos nada de vuestro sistema: el cuerpo que nos enviáis es el lead, y todo lo que os devolvemos viaja dentro del evento. Calligence nunca abre una conexión hacia vosotros que no sea el POST a la URL que nos habéis dado.

Antes de empezar: qué hace falta y quién lo aporta

#
Qué Quién lo aporta Para qué
La conexión creada en Calligence (Integraciones → + Nueva conexión → Webhook (genérico)) Calligence Al crearla se generan los secretos y se muestran una sola vez. Guárdalos antes de cerrar la ventana; recuperarlos es imposible, solo se pueden rotar.
La URL de webhook de cada campaña y el secreto de entrada Calligence Son las dos credenciales de la mitad de entrada. Ambas son contraseñas.
El mapeo de entrada: qué clave de vuestro JSON es el teléfono, el id, el nombre, el producto… Vuestro equipo Lo configuramos en la conexión. Nos adaptamos a vuestro payload; no hace falta que cambiéis el vuestro. Ver el mapeo de entrada.
Un endpoint HTTPS público que acepte POST con cuerpo JSON Vuestro equipo Solo si queréis la mitad de salida. Ver requisitos de la URL.
El secreto de firma y, si vuestro endpoint lo pide, una cabecera fija (nombre y valor) Calligence Vuestro equipo El secreto de firma lo generamos nosotros y con él verificáis cada entrega. La cabecera fija (por ejemplo Authorization) la ponéis vosotros si vuestro endpoint ya exige una.
El teléfono del lead y su consentimiento para recibir la llamada Vuestro equipo Es el único campo obligatorio. Sin un teléfono válido la petición se rechaza con 422.

Entrada — enviadnos leads

#

La petición

#
POST https://calligence.ai/api/connectors/webhook/<token-de-la-campaña>
Content-Type: application/json
X-Calligence-Secret: <secreto de entrada de la conexión>

{
  "id": "A-1027",
  "telefono": "+34600111222",
  "nombre": "María López",
  "curso": "Oposiciones Administrativo",
  "codigo": "OPO-ADM",
  "nivel_estudios": "Bachillerato"
}
  • Una URL por campaña. La URL decide en qué campaña entra el lead, qué agente le llama y con qué cadencia. La copiáis desde la campaña en Calligence (Campañas → la campaña → Copiar).
  • La cabecera X-Calligence-Secret es obligatoria y es la misma para todas las campañas de esa conexión.
  • Un objeto JSON por petición. No aceptamos arrays ni lotes: si tenéis diez leads, son diez peticiones. Tampoco aceptamos application/x-www-form-urlencoded ni multipart — el cuerpo tiene que ser JSON.
  • Máximo 64 KiB por petición.
  • Las claves son las vuestras: nos decís cuál es cuál y lo configuramos en la conexión.

El mapeo de entrada

#

En la conexión configuramos, para cada campo que necesitamos, la clave de vuestro payload que lo contiene. Se configura una vez y vale para todas las campañas de la conexión.

Campo de Calligence Qué es Si falta
phone obligatorio El número al que llamamos. Formato recomendado +34600111222; sin prefijo internacional se asume España. Vale como texto o como número. 422. Un lead sin teléfono no es un lead que este motor pueda trabajar.
external_id Vuestro identificador del lead. Es lo que os devolvemos en cada evento de salida y lo que usamos para reconocer un reenvío. El lead entra igual. Identificamos el envío por teléfono + día, y en la salida os llega external_id: null con el phone al lado. Ver reenvíos.
name, o bien first_name + last_name El nombre con el que el agente saluda. Las dos formas son alternativas: o una, o la otra, no las dos a la vez. Un nombre de más de 100 caracteres se recorta a 100. El lead entra sin nombre y se le llama igual, pero el agente no puede dirigirse a él por su nombre.
product_name y product_code Opcional, pero muy importante. El producto por el que se interesó el lead — el curso, el tratamiento, el plan, el producto — y su código, si lo tenéis. El agente los recibe como variables y habla de ese producto. El lead entra y se llama igual, con el producto configurado en la campaña o sin mencionar ninguno.
variable_fields Pares clave de vuestro payload → nombre de la variable que recibe el agente. Por ejemplo {"nivel_estudios": "nivel"}. Nada: solo cambia qué información adicional recibe el agente.
keep_unmapped (por defecto: sí) Conservar los campos no mapeados. Con esto activado, todas las claves de vuestro JSON que no haya consumido el mapeo llegan al agente como información adicional, con su propio nombre. Un campo que nombréis explícitamente en variable_fields gana sobre la copia automática. Si lo desactivamos, el agente solo recibe lo que esté mapeado.

Con el ejemplo de arriba, el mapeo sería phone → telefono, external_id → id, name → nombre, product_name → curso, product_code → codigo y variable_fields: {"nivel_estudios": "nivel"}. Si añadís una clave nueva a vuestro payload y no nos decís nada, con conservar campos no mapeados activo el agente la recibirá igualmente como información adicional.

Qué respondemos

#
Código Cuerpo Qué significa y qué hacer
200 {"status": "accepted"} Aceptado. El lead entra en la campaña. También es la respuesta a un reenvío ya procesado, que no cambia nada.
422 {"error": "El cuerpo debe ser un objeto JSON."} El cuerpo no es un objeto JSON (un array, un fragmento, un formulario) o lleva valores que no son JSON estándar (NaN, Infinity o un número que desborda, como 1e999). Corregid el Content-Type y el cuerpo.
422 {"error": "Falta el teléfono (campo «telefono»)."} La clave mapeada como teléfono no viene, o viene vacía. El mensaje os dice qué clave estamos mirando.
422 {"error": "El teléfono no es válido: «600 11»."} El valor está, pero no es un número al que se pueda llamar. El mensaje os devuelve lo que recibimos (recortado).
413 {"error": "El cuerpo es demasiado grande."} Más de 64 KiB. Enviad solo los campos del lead.
429 {"error": "Se ha alcanzado el límite diario de leads para esta campaña."} Límite de leads del día (300 por campaña y día UTC, por defecto). Reanudad mañana o avisadnos para subirlo.
429 {"error": "Demasiadas solicitudes hoy para esta campaña."} Tope de peticiones del día (500 por campaña y día UTC, por defecto). Normalmente indica un bucle en vuestro lado.
404 {"detail": "Not Found"} Algo va mal con las credenciales. Ver el aviso de abajo.
5xx — Un error nuestro. El envío no ha quedado registrado: reintentadlo.

El 404 es deliberadamente ambiguo

Respondemos exactamente el mismo 404 a una URL que no existe, a una cabecera X-Calligence-Secret ausente o incorrecta, a un secreto válido pero emitido para otra conexión, y a una conexión desactivada. Es una decisión de seguridad: quien encuentre una de vuestras URLs no debe poder averiguar si existe ni si el secreto que ha probado es el bueno. La contrapartida es que un 404 no os dice cuál de las cuatro cosas ha pasado. Revisad, por este orden: que la URL es la de la campaña (y no se ha rotado), que enviáis la cabecera, que el secreto es el de esta conexión, y que la conexión sigue activa en Calligence. Si tras eso sigue fallando, escribidnos: nosotros sí vemos el motivo.

Reenvíos y duplicados

#

Cada lead se identifica por su id dentro de la campaña — el campo que hayáis mapeado a external_id. Si nos reenviáis el mismo id antes de que lo hayamos procesado con éxito, usamos los datos nuevos (podéis corregir un teléfono así). Una vez procesado correctamente, ese id queda cerrado para esa campaña: un reenvío posterior responde 200 pero no actualiza nada, y reenviarlo con un id distinto no lo corrige: crea un lead nuevo y una llamada nueva a ese número, aunque el primero siga en curso. Para corregir un lead ya procesado, avisadnos.

  • El identificador no caduca. Un reenvío del mismo id a la misma campaña la semana que viene sigue siendo el mismo lead. Es la sorpresa más habitual de esta integración: si vuestro sistema reenvía el lead cada vez que se edita, el segundo envío no hará nada.
  • El mismo id en otra campaña sí es otro lead, porque cada campaña es una gestión distinta.
  • Sin id, identificamos el envío por teléfono + día UTC: dos envíos del mismo número el mismo día son uno. Un reenvío otro día llega al mismo cliente y al mismo lead: no crea un lead nuevo. Y si más adelante ese teléfono nos llega con un id —en un envío, en un CSV con columna de id o desde un CRM—, el id se asigna a ese mismo cliente: no se crea un segundo cliente al que volver a llamar.
  • Un cuerpo rechazado (422 o 413) se registra por su contenido: reenviar el mismo cuerpo idéntico no crea nada nuevo, mientras que un cuerpo corregido entra como un intento nuevo.

Los dos límites diarios

#

Ambos son por campaña y por día UTC, y ambos responden 429 con mensajes distintos:

  • 300 leads llamables al día, por defecto — el techo de llamadas. Un lead que llega por encima de este límite no se llama, salvo que nos lo reenviéis (con el mismo id) otro día UTC. La respuesta es «Se ha alcanzado el límite diario de leads para esta campaña».
  • 1.500 envíos registrados al día, por defecto: cinco veces el techo de llamadas — el tope duro. Se comprueba antes que cualquier otra cosa y protege el sistema de un bucle en el origen: por encima de él no registramos nada más. La respuesta es «Demasiadas solicitudes hoy para esta campaña». Los reenvíos idénticos, que no crean registro nuevo, no consumen este tope.

Los dos números son configuración nuestra, no vuestra: los ajustamos para toda vuestra organización o para una campaña concreta, y el tope duro siempre es cinco veces el techo de llamadas. Si vuestro volumen normal los supera, decídnoslo antes de empezar.

Salida — os enviamos los resultados

#

Hacemos POST a vuestro endpoint un objeto JSON plano y versionado, con las mismas claves para todos los eventos y null en las que no apliquen. Así escribís un parser, una vez: añadir un evento o un campo nuevo no os rompe nada.

Los cuatro eventos

#
event Cuándo Campos con valor
note Tras cada llamada, cuando un usuario de Calligence registra o corrige el resultado del lead, cuando el asistente de WhatsApp acuerda una cita o registra que el lead no está interesado, no cumple los requisitos o pide no ser contactado, y cuando un operador lo marca como «trabajado fuera». note — el resumen en español, que empieza por «Calligence — resumen de la llamada:» y termina con el resultado. El de «trabajado fuera» lleva call_id: null y el texto «Calligence dejó de llamar a este lead.», seguido de «Motivo: …» si el operador lo indicó. El de un resultado registrado también lleva call_id: null y empieza por «Calligence — resultado registrado por {usuario}», o por «Resultado corregido de … a … por {usuario}» si es una corrección. El de una cita acordada por WhatsApp, con call_id: null, empieza por «Calligence — cita acordada por WhatsApp». El de lo que el lead decide por WhatsApp, también con call_id: null, es «Calligence — por WhatsApp: No interesado», «… Inválido» (seguido del motivo, si se conoce) o «… Pide no ser contactado».
status Cuando el lead llega a un estado final, por una llamada, porque un usuario de Calligence registra o corrige su resultado, o porque el asistente de WhatsApp acuerda una cita o registra que el lead no está interesado, no cumple los requisitos o pide no ser contactado (estos, con call_id: null). Si después el lead vuelve a escribir que sí y pide que le llamen, Calligence lo reabre sin enviar un estado nuevo: el último que os llegó sigue ahí hasta el siguiente estado final. Una vez por resultado: si el resultado cambia, os llega otro. status (EXITO, NO_INTERESADO o INVALIDO) y tags, la misma etiqueta en español que reciben los CRM: ["Calligence: Éxito"].
appointment Cuando la llamada acuerda una cita con día y hora concretos o el cliente acepta que le llame un asesor sin decir hora («lo antes posible», «hoy mismo»: entonces scheduled_at es el momento en que terminó la llamada), o cuando un usuario de Calligence registra una cita o el asistente de WhatsApp la acuerda con día y hora o «lo antes posible» (call_id: null). scheduled_at (ISO-8601 con desfase horario) y title (Cita Calligence — {nombre}).
ping Solo cuando un operador pulsa Probar en Calligence. Ninguno: todo null salvo version, event y sent_at. No corresponde a ningún lead.

El evento de cita no es opcional. Si la mitad de salida está activa, os llegará el appointment siempre que una llamada acuerde un momento concreto o «lo antes posible» — a diferencia de las conexiones con un CRM, donde hay que configurar un calendario para activarlo. Aquí no hay calendario que configurar: el evento lleva el momento y vuestro sistema decide qué hacer con él. Si no os interesa, ignoradlo mirando el campo event.

Un lead cerrado desde fuera de Calligence no genera evento status: si nos decís que ya lo habéis trabajado, no os devolvemos un resultado que contradiga al vuestro.

El esquema, entero

#

Estas quince claves viajan siempre, en todos los eventos:

Clave Tipo Qué es
versionenteroVersión del esquema. Hoy 1. Las claves y valores nuevos llegan sin cambiarlo; solo cambia con un cambio incompatible, que elegiríais conexión a conexión. Ver versiones y compatibilidad.
eventtextonote, status, appointment o ping.
event_identero o nullEl identificador de la entrega. Único y estable entre reintentos: es por aquí por donde deduplicáis. null en un ping.
sent_attexto ISO-8601 UTCEl momento de este intento. Cambia en cada reintento, así que nunca es una identidad.
campaign_identero o nullLa campaña de Calligence.
lead_identero o nullLa gestión (el lead dentro de la campaña) en Calligence.
call_identero o nullLa llamada concreta. null cuando el evento no nace de una llamada.
external_idtexto o nullVuestro identificador, tal y como nos lo enviasteis. null si el lead entró sin id: en ese caso usad el phone.
phonetexto o nullEl teléfono del cliente, normalizado a E.164.
nametexto o nullEl nombre del cliente. null si no lo conocemos.
notetexto o nullEl resumen de la llamada, o el aviso de «trabajado fuera». Solo en note.
statustexto o nullEl resultado final. Solo en status.
tagslista o nullLa etiqueta en español del resultado. Solo en status.
scheduled_attexto o nullEl momento acordado, ISO-8601 con desfase. Solo en appointment.
titletexto o nullEl título de la cita. Solo en appointment.

Un evento status, así de legible:

{
  "version": 1,
  "event": "status",
  "event_id": 40213,
  "sent_at": "2026-09-09T10:14:03+00:00",
  "campaign_id": 27,
  "lead_id": 5561,
  "call_id": 9182,
  "external_id": "A-1027",
  "phone": "+34600111222",
  "name": "María López",
  "note": null,
  "status": "EXITO",
  "tags": ["Calligence: Éxito"],
  "scheduled_at": null,
  "title": null
}

En la petición viaja compacto, ordenado y en ASCII

El cuerpo real no lleva espacios ni saltos de línea, las claves van ordenadas alfabéticamente y los caracteres no ASCII viajan escapados (María viaja como Mar\u00eda). El escapado es deliberado: hace que la firma sea comprobable sin ambigüedad de codificación en cualquier lenguaje. Vuestro parser de JSON lo deshace solo — no tenéis que hacer nada, salvo no volver a serializar el cuerpo antes de verificar la firma. Este es el cuerpo exacto de un ping:

{"call_id":null,"campaign_id":null,"event":"ping","event_id":null,"external_id":null,"lead_id":null,"name":null,"note":null,"phone":null,"scheduled_at":null,"sent_at":"2026-09-09T10:14:03+00:00","status":null,"tags":null,"title":null,"version":1}

Renombrar o quitar claves

#

Si vuestro endpoint ya existe y espera otros nombres, no hace falta que lo cambiéis: configuramos en la conexión un mapeo de salida que renombra o quita claves de primer nivel. Es un renombrado, no una transformación: los valores no se tocan, no se anida nada y no se calcula nada.

Por ejemplo, un endpoint que espera {id, tags, note, expected_datetime}:

{
  "external_id":  "id",                  ← renombra
  "scheduled_at": "expected_datetime",   ← renombra
  "campaign_id":  null,                  ← no enviar
  "lead_id":      null,
  "call_id":      null,
  "sent_at":      null,
  "phone":        null,
  "name":         null,
  "status":       null,
  "title":        null
}

Con ese mapeo, vuestro endpoint recibe {version, event, event_id, id, note, tags, expected_datetime}. Dos reglas: version, event y event_id pueden renombrarse pero no quitarse (sin ellas, el cuerpo no se puede enrutar, versionar ni deduplicar), y dos campos no pueden acabar con la misma clave — uno taparía al otro sin que nadie se enterase, así que el guardado se rechaza.

La firma: comprobad que el evento es nuestro

#

Cada entrega lleva la cabecera:

X-Calligence-Signature: t=1757412843,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
  • t es el momento de la firma, en segundos Unix.
  • v1 es un HMAC-SHA256 en hexadecimal minúsculas, calculado con vuestro secreto de firma sobre la cadena "<t>." + cuerpo — el valor de t, un punto, y el cuerpo en crudo.
  • La etiqueta es v1 a propósito: si vuestro sistema también recibe webhooks de otros proveedores con este mismo formato, la versión os dice de quién es cada firma.

Firmad sobre el cuerpo EN CRUDO — este es el error que se comete siempre

El HMAC se calcula sobre los bytes exactos que llegan. Si vuestro framework os entrega el cuerpo ya convertido en un objeto o un mapa y lo volvéis a serializar para verificar, el resultado no coincidirá: cambia un espacio, el orden de una clave o el escapado, y el HMAC es otro. Pedid siempre el cuerpo sin procesar (raw body) y verificad sobre él; parsead el JSON después.

En pseudocódigo, sirva para cualquier lenguaje:

cabecera = headers["X-Calligence-Signature"]     # "t=...,v1=..."
t  = valor de "t"  en la cabecera
v1 = valor de "v1" en la cabecera

# 1) Ventana de repetición: descartad lo que venga con mucho retraso.
si abs(ahora_unix() - entero(t)) > 300:
    rechazar 401                                  # 300 s = nuestra propia tolerancia

# 2) El HMAC, sobre el cuerpo en crudo.
esperado = hex(HMAC_SHA256(clave = SECRETO_DE_FIRMA,
                           mensaje = t + "." + cuerpo_en_crudo))

# 3) Comparación en tiempo constante (nunca ==).
si no comparacion_segura(esperado, v1):
    rechazar 401

responder 200

Y en Deluge, para quien verifique dentro de Zoho:

// El cuerpo TAL CUAL llega. No uséis el mapa ya parseado.
cuerpo = crmAPIRequest.get("body");
cabecera = crmAPIRequest.get("headers").get("X-Calligence-Signature");

t  = cabecera.getSuffix("t=").getPrefix(",");
v1 = cabecera.getSuffix("v1=");

// Tercer argumento "hex": v1 viaja en hexadecimal. Sin él, Deluge
// devuelve base64 y la comparación no coincidirá nunca.
esperado = zoho.encryption.hmacsha256("<secreto de firma>", t + "." + cuerpo, "hex");

if (esperado.equalsIgnoreCase(v1))
{
  // Evento auténtico: ya podéis parsear el JSON.
}

Sobre la ventana temporal: nosotros firmamos con la hora del envío y, cuando verificamos una firma nuestra, rechazamos lo que tenga más de 5 minutos. Usad ese mismo margen salvo que vuestro reloj no esté sincronizado por NTP, en cuyo caso ampliadlo un poco antes que desactivarlo. Recordad que un reintento nuestro llega con una t y un sent_at nuevos, así que no os quedaréis fuera de ventana por un reintento.

Una cabecera fija, si vuestro endpoint la exige

#

Además de la firma, podemos enviar en cada entrega una cabecera con nombre y valor fijos — un Authorization: Bearer …, una API key propia. La guardamos cifrada junto al resto de credenciales de la conexión y no aparece en ningún sitio de la interfaz. Tiene que ser una cabecera HTTP válida, y lo comprobamos al guardarla y otra vez en cada envío: el nombre, de 1 a 64 caracteres, solo con letras sin tilde, dígitos y !#$%&'*+-.^_`|~, y que no sea —da igual en mayúsculas o minúsculas— Content-Type, Content-Length, Host, Transfer-Encoding, Connection ni X-Calligence-Signature; el valor, de 1 a 512 caracteres ASCII imprimibles (nada de saltos de línea, caracteres de control, tildes ni un espacio de no separación pegado). Una cabecera que no cumple no se guarda, y si una ya guardada no cumple, la entrega falla sin reintento. Si vuestra URL ya lleva su propia clave en la query string, tampoco hay problema: la respetamos literalmente, tal y como nos la deis. Por eso la URL de destino es en sí misma una credencial, y no la mostramos entera en Calligence ni la escribimos en ningún registro.

Reglas de entrega

#
Vuestra respuesta Qué hacemos
2xx Entregado. Es lo único que contamos como éxito. Respondednos rápido y haced el trabajo pesado después.
3xx Fallo definitivo, sin reintento. No seguimos redirecciones — una redirección puede llevar a cualquier sitio, y la firma es del cuerpo, no del destino. Si vuestra URL cambia, dadnos la URL final.
4xx Fallo definitivo, sin reintento, salvo 408 y 429, que reintentamos como un 5xx. Por lo demás, la misma petición no va a funcionar mejor la segunda vez. Queda registrada como fallida y visible en Calligence. Un 401 o 403 se marca además como problema de credenciales.
5xx, timeout o error de red Reintentamos. Nuestro tiempo de espera por intento es de 10 segundos. Tras agotar los reintentos, la entrega queda marcada como fallida.
  • Deduplicad por event_id, nunca por el cuerpo. Un reintento lleva el mismo event_id pero un sent_at y una firma distintos, así que dos cuerpos idénticos no existen y comparar cuerpos os haría procesar todo dos veces.
  • Diseñad el endpoint como idempotente. Podemos entregar el mismo event_id más de una vez (un reintento que en realidad sí había llegado).
  • El orden no está garantizado. Si os importa, ordenad por event_id, que es creciente.

Requisitos de la URL de destino

#
  • HTTPS obligatorio. Nada de http://.
  • Un nombre de dominio público. Rechazamos localhost, los dominios internos y cualquier dirección IP literal — también las públicas: una URL de producción se escribe con su dominio. La comprobación se hace al guardar la URL y otra vez en cada envío, así que una URL editada a mano se niega a enviar en lugar de fiarse.
  • Sin puerto explícito y sin usuario/contraseña en la URL (https://usuario:clave@… no vale). Si necesitáis autenticación, usad la cabecera fija.
  • La query string se conserva literalmente, tal y como nos la deis.

Las credenciales y qué corta cada una

#

Hay tres credenciales en juego y cada una tiene un alcance distinto. Vale la pena leer esta tabla antes de rotar nada: dos de ellas afectan a todas las campañas de la conexión.

Credencial Alcance Cómo se revoca y qué se rompe
El token de la URL
la parte final de la URL de webhook
Una campaña Rotándolo desde la campaña (Campañas → Editar → Rotar). Deja de funcionar la URL de esa campaña y ninguna otra. Es lo que hay que hacer si se filtra una URL concreta. Desvincular la conexión (mismo sitio) también la anula, y además deja la campaña sin conexión.
El secreto de entrada
X-Calligence-Secret
Toda la conexión Rotándolo en la conexión: se genera uno nuevo, se muestra una vez y el anterior deja de valer. Desde ese momento dejan de entrar leads por todas las campañas de esa conexión hasta que el emisor use el nuevo. Si la fila de la conexión muestra «sin secreto activo», la conexión no tiene ningún secreto de entrada válido y toda la entrada responde 404: rotadlo para generar uno nuevo.
El secreto de firma
el de la mitad de salida
Toda la conexión Solo se rota. Desde la rotación, toda entrega va firmada con el nuevo secreto — si no lo actualizáis en vuestro endpoint, todas las verificaciones empezarán a fallar. Rotadlo cuando vayáis a poder cambiarlo también en vuestro lado.

Los dos secretos se muestran una sola vez, en el momento de crearlos o rotarlos. No los guardamos en claro y no hay forma de volver a verlos: si se pierde la copia, la única salida es rotar. Es la consecuencia correcta para una credencial que tenéis vosotros.

Y una nota sobre identidades: si nos enviáis un lead sin id y su teléfono ya existía en vuestra organización de Calligence con un identificador de otro sistema (por ejemplo, porque ese cliente entró antes por un CRM), los eventos de salida llevarán ese identificador en external_id. No sobrescribimos la identidad de un cliente que ya tenía una. Si lo enviáis con id y ese teléfono pertenece a un cliente con otro identificador, se crea un cliente aparte con el vuestro. Si os encontráis un external_id que no reconocéis, comparad por phone.

Probar la conexión

#

En Integraciones, el botón Probar de la conexión envía a vuestro endpoint un evento ping real: mismo cuerpo canónico, misma firma, mismas cabeceras y el mismo tratamiento de la respuesta que una entrega de verdad. Es la forma de comprobar la mitad de salida sin esperar a una llamada. No crea ningún lead ni deja ningún registro de sincronización.

  • Correcta — vuestro endpoint respondió 2xx.
  • Credenciales rechazadas — respondió 401 o 403: normalmente la firma no cuadra (revisad que firmáis sobre el cuerpo en crudo) o falta la cabecera fija.
  • Sin determinar — cualquier otra cosa: un 5xx, una redirección, un timeout, o una URL guardada que ya no cumple los requisitos. Que no podamos comprobarlo no significa que esté mal, y por eso no lo pintamos en rojo.

La prueba solo cubre la mitad de salida: no dice nada sobre si vuestros envíos de entrada están llegando. Una conexión que solo tiene entrada no tiene a quién llamar, así que responde Correcta sin enviar nada. Para lo que os rechazamos a vosotros está la lista de Últimos rechazos de la conexión, con la fecha, el motivo y la campaña de las últimas peticiones refusadas.

Solución de problemas

#

Recibo 404 en todo

#

Es la respuesta única a los cuatro fallos de credenciales. Comprobad: la URL (¿se rotó el token de la campaña?), que enviáis la cabecera X-Calligence-Secret, que su valor es el de esta conexión (un secreto válido de otra conexión también da 404) y que la conexión está activa. Si la fila muestra «sin secreto activo», la conexión no tiene ningún secreto de entrada válido: rotadlo para generar uno nuevo.

Envié el lead, respondió 200 y no aparece nada nuevo

#

Lo más probable es que sea un reenvío de un id que ya procesamos en esa campaña: la respuesta es 200 pero no actualiza nada. Si usáis un id distinto para la prueba, tened en cuenta que es un lead nuevo: volveremos a llamar a ese número.

La firma nunca me cuadra

#

Casi siempre es que se está verificando sobre un cuerpo re-serializado en lugar del crudo. Comprobad también que firmáis t + "." + cuerpo (con el punto), que comparáis hexadecimal en minúsculas y que usáis el secreto de firma, no el de entrada. Truco de diagnóstico: haced que vuestro endpoint registre el largo en bytes del cuerpo recibido y comparadlo con el que anuncia Content-Length; si no coinciden, algo está reprocesando el cuerpo antes de que lleguéis vosotros.

Me llegan eventos repetidos

#

Es esperable: reintentamos ante un 5xx o un timeout, y un reintento puede llegar aunque el primero sí se procesara. Deduplicad por event_id. Si estáis comparando cuerpos, no funcionará: sent_at cambia en cada intento.

No me llega ningún evento de salida

#

Pulsad Probar: si responde Correcta, el circuito funciona y lo que falta es que haya llamadas terminadas. Si la conexión se creó solo con entrada, no hay nada configurado que enviar — hay que añadir la URL de destino a la conexión. Y comprobad que la conexión sigue activa: una conexión desactivada no envía ningún evento.

El agente no menciona el producto, o menciona otro

#

Revisad que el envío incluye la clave mapeada como product_name (y product_code, si lo usáis) con un valor estable. Si trabajáis una campaña por producto, comprobad también que cada lead va a la URL de su campaña: el campo dice de qué habla el agente, la URL decide qué agente llama.

Empecé a recibir 429

#

Mirad el mensaje: «límite diario de leads» significa que se ha llegado al techo de llamadas del día y que ese lead no se llamará salvo que lo reenviéis (con el mismo id) otro día UTC; «demasiadas solicitudes» es el tope duro de peticiones, y suele indicar un bucle en el origen. Escribidnos y ajustamos los límites a vuestro volumen.

¿Sigues atascado? Escríbenos y lo miramos contigo.