Webhooks
Los dos webhooks del agente en Atendy: el de entrada (antes de la llamada) y el de fin de llamada, con la estructura del payload y buenas prácticas.
Webhooks (API)
Un webhook es una URL tuya (o de una herramienta como n8n, Make o tu propio servidor) a la que Atendy envía datos de forma automática en un momento concreto de la llamada. Cada agente puede tener dos webhooks, y sirven para cosas distintas: uno se dispara antes de que empiece la conversación y el otro cuando la llamada termina.
Ambos se configuran dentro del agente, en su asistente de herramientas o ajustes del agente, pegando la URL correspondiente. Funcionan tanto en llamadas de teléfono como en las pruebas desde el navegador.
Webhook de entrada (antes de la llamada)
En cuanto entra una llamada (o empiezas una prueba), Atendy hace una petición a tu URL de entrada con los datos que ya conoce, sobre todo el número de teléfono de quien llama. Tu servidor puede usar ese número para buscar a la persona en tu sistema y responder con datos suyos. Todo lo que devuelvas queda disponible como variables dentro del prompt y del saludo.
- Prepara una URL en tu servidor o herramienta que reciba una petición POST con JSON.
- En el agente, pega esa URL en el campo del webhook de entrada.
- Cuando llegue una llamada, Atendy enviará a esa URL el número de origen y el identificador del agente.
- Tu servidor responde con un JSON que contenga las variables que quieras usar (por ejemplo el nombre del cliente).
- El agente arranca la conversación ya con esos datos cargados.
Esto es un ejemplo de lo que Atendy envía a tu webhook de entrada:
{
"agent_id": "a1b2c3d4-0000-0000-0000-000000000000",
"caller_number": "+34600123456",
"room_name": "call-_+34600123456_a1B2c3"
}Y esto es lo que tu servidor debe responder: un JSON con las variables que quieras poner a disposición del agente. Los nombres de las claves son los que luego usarás entre dobles llaves.
{
"nombre": "Ana García",
"plan": "Premium",
"ultima_cita": "2026-07-02"
}Usar las variables en el prompt y el saludo
Cada variable que devuelva el webhook de entrada se escribe entre dobles llaves y Atendy la sustituye por su valor real justo antes de hablar. Puedes usarlas tanto en el prompt del sistema como en el saludo inicial.
Saludo inicial:
Hola {{nombre}}, gracias por llamar. Veo que tienes el plan {{plan}}.
Prompt del sistema:
Atiendes a {{nombre}}, cliente con plan {{plan}}.
Su última cita fue el {{ultima_cita}}. Ayúdale con lo que necesite
sobre su cuenta y confirma sus datos si hace falta.Webhook de fin de llamada
Cuando la llamada termina y Atendy ha analizado la conversación, envía a tu URL de fin de llamada un único mensaje con lo ocurrido: la transcripción completa, un resumen, los KPIs que hayas definido y los datos de la llamada. Es el momento ideal para volcar el resultado en tu CRM, avisar a tu equipo o lanzar un flujo en n8n.
El payload usa la misma estructura que el evento call_analyzed de Retell, para que los flujos ya construidos para ese formato funcionen sin cambios. Todo cuelga de un objeto call:
{
"event": "call_analyzed",
"call": {
"call_id": "call-out-4f3a9c2b1d7e",
"agent_id": "a1b2c3d4-0000-0000-0000-000000000000",
"agent_name": "Recepción",
"call_status": "ended",
"start_timestamp": 1786303502000,
"end_timestamp": 1786303645000,
"duration_ms": 143000,
"from_number": "+34911234567",
"to_number": "+34600123456",
"direction": "outbound",
"disconnection_reason": "agent_hangup",
"transcript": "Agent: Hola Ana, le llamo por su cita.\nUser: Sí, quería cambiarla.",
"transcript_object": [
{ "role": "agent", "content": "Hola Ana, le llamo por su cita." },
{ "role": "user", "content": "Sí, quería cambiarla." }
],
"call_analysis": {
"call_summary": "La clienta pidió cambiar su cita del jueves al viernes por la tarde. Se reprogramó y se le confirmó.",
"custom_analysis_data": {
"cita_reprogramada": true,
"motivo": "cambio de fecha"
}
}
}
}Campos que recibes, todos dentro de call:
| Campo | Qué contiene |
|---|---|
| event | Siempre call_analyzed. Va en la raíz, no dentro de call. |
| call_id | Identificador de la llamada. En una llamada lanzada por API es exactamente el call_id que te devolvió el POST, así que es la forma de casar tu petición con su resultado. |
| agent_id / agent_name | El agente que atendió la llamada. |
| call_status | Siempre ended: este aviso solo se envía con la llamada terminada. |
| direction | inbound en llamadas entrantes, outbound en salientes y web en las pruebas desde el navegador. |
| from_number / to_number | Números de la llamada según su dirección. En una saliente, from_number es tu número y to_number el de la persona a la que llamaste. |
| start_timestamp / end_timestamp | Momentos de inicio y fin en milisegundos desde el 1 de enero de 1970 (epoch), no en texto con fecha. Valen 0 si el inicio no se pudo determinar. |
| duration_ms | Duración en MILISEGUNDOS. Ojo si vienes de una integración que esperaba segundos. |
| disconnection_reason | Cómo terminó: agent_hangup, user_hangup, call_transfer, max_duration_reached o error_llm_websocket_runtime. Este último significa que la cortó el sistema porque el agente dejó de responder o falló el modelo de voz, no que colgara la persona. En la llamada se guarda como terminada por el sistema (ended_by=system): en el Resumen cuenta como Sistema y en el registro de herramientas del Historial, fin_de_llamada dice «cortada por el sistema». |
| transcript | La conversación como texto plano, con cada turno en una línea con el prefijo Agent: o User:. |
| transcript_object | La misma conversación turno a turno, con role (agent o user) y content. Fíjate en que el texto va en content, no en text. |
| call_analysis.call_summary | El resumen de la llamada. |
| call_analysis.custom_analysis_data | Los datos clave que le pediste extraer al agente (por ejemplo, si se cerró una cita). |
| crm_client | Extensión de Atendy (no existe en Retell): la ficha del CRM de la persona que llamó o a la que llamaste, con name y client_ref (su número de cliente), para volcar la llamada en la fila correcta sin cruzar teléfonos. Es null si tu cuenta no usa el CRM o esa persona aún no tiene ficha (la ficha de un desconocido se crea justo después de este aviso, así que en su primera llamada llega null). |
| metadata | El objeto metadata que enviaste al lanzar la llamada por la API (POST /api/v1/calls), devuelto TAL CUAL: sirve para casar la llamada con el registro de tu sistema sin guardar el call_id. Es {} si no enviaste nada (y en llamadas entrantes, siempre). |
| answered_at / connected_seconds | Extensión de Atendy: momento (epoch en milisegundos) en que la otra parte DESCOLGÓ y segundos de conversación desde el descuelgue, sin contar el timbrado. En una saliente, answered_at null es la señal fiable de que nadie contestó. |
| amd_class / machine_detected | Extensión de Atendy, solo salientes: si el agente detectó un contestador o buzón de voz, machine_detected llega true y amd_class dice la clase (machine_vm, machine_ivr o machine_unavailable). En una conversación con una persona, false y null. |
Buenas prácticas
Receta: cada llamada en una fila de tu Excel o Google Sheets (n8n)
Es lo que más se pide: que cada llamada quede apuntada en la hoja donde el negocio ya lleva sus clientes, esté en Google Sheets, en OneDrive o en Dropbox. Se hace con el webhook de fin de llamada y un flujo de n8n de tres nodos. Con Make o Zapier es igual, cambian los nombres.
- En n8n crea un flujo nuevo y añade un nodo Webhook con método POST. Copia la URL de producción que te da.
- En Atendy, abre el agente → Integraciones y pega esa URL en «Webhook de agente · post-llamada». Guarda.
- Haz una llamada de prueba al agente (o usa Probar agente). Al minuto llegará a n8n el evento call_analyzed: en el nodo Webhook pulsa «Listen for test event» antes de llamar para ver los campos.
- Añade un nodo Google Sheets (acción «Append row») o Microsoft Excel 365 («Add a row») y apunta a tu hoja. Si tu archivo está en Dropbox, n8n tiene nodo de Dropbox pero no edita celdas: para Excel en Dropbox lo práctico es que la hoja viva en OneDrive o en Google Sheets, o mover el archivo.
- Mapea las columnas arrastrando desde el evento: fecha = {{ $json.call.start_timestamp }} (milisegundos; conviértelo con DateTime.fromMillis), teléfono = call.from_number en entrantes y call.to_number en salientes, nombre = call.crm_client.name, número de cliente = call.crm_client.client_ref, resumen = call.call_analysis.call_summary, y cada Dato del agente = call.call_analysis.custom_analysis_data.<nombre_del_dato>. Guarda también call.call_id: es tu referencia única.
- Si quieres actualizar la fila del cliente en vez de añadir una por llamada, usa la acción «Update row» con la columna del teléfono (o del número de cliente) como clave de búsqueda, y añade las columnas nuevas a la derecha de las tuyas.
- Activa el flujo. A partir de ahí cada llamada aparece en tu hoja un par de minutos después de colgar.