Webhooks entrantes
Enrutamiento dinámico de agentes: Finn llama a tu servidor cuando llega una llamada entrante.
Webhooks entrantes
Cuando llega una llamada entrante, Finn puede preguntar a tu servidor en tiempo real con qué agente responder, qué variables inyectar y qué base de conocimiento cargar. Esto te permite enrutar de forma dinámica: por número del llamante, nivel de cuenta, hora del día, cohorte de A/B o cualquier lógica que le importe a tu backend.
Si con un agente fijo te basta, omite esta página. Si quieres enrutamiento dinámico por llamada, esta es la página.
Cómo funciona
caller dials → Finn answers ring → Finn POSTs to your URL → your server returns agent + vars → Finn streams the voice agent
El intercambio añade unos 100-200 ms de latencia. Tu endpoint debe responder en menos de 500 ms o Finn recurrirá al agente predeterminado.
Configurar
1. Define la URL del webhook entrante
Panel → Configuración → Números de teléfono → [número] → URL del webhook entrante.
Pega tu endpoint HTTPS. Guarda. Finn lo consulta una vez con un cuerpo {"ping": true} para verificar que es accesible.
Mediante API
curl https://api.hirefinn.ai/v1/phone-numbers/ph_1234 \
-X PATCH \
-H "Authorization: Bearer $FINN_API_KEY" \
-d '{ "inbound_webhook_url": "https://your-app.com/finn/inbound" }'
2. Define un agente de respaldo
Configura un Finn predeterminado para ese número: se usa si tu webhook agota el tiempo de espera, falla o devuelve una respuesta no válida.
Carga útil de la solicitud
Finn envía un POST a tu URL con:
{
"session_id": "ws_2H4abc",
"request_type": "inbound",
"phone_number_id": "ph_1234",
"channel": "pstn",
"from": {
"phone": "+919876543210",
"country": "IN",
"carrier": "Airtel"
},
"to": {
"phone": "+918765432100",
"country": "IN"
},
"received_at": "2026-05-22T14:30:00Z",
"metadata": {}
}
En las llamadas de WhatsApp, el bloque from usa campos específicos de WhatsApp:
"from": {
"wa_id": "919876543210",
"display_name": "Priya M",
"profile_pic_url": "https://..."
},
"channel": "whatsapp"
Respuesta
Devuelve un cuerpo JSON en menos de 500 ms que describa cómo enrutar la llamada:
{
"finn_id": "fn_def456",
"language": "hi-IN",
"variables": {
"customer_name": "Priya M",
"account_tier": "premium",
"last_order_id": "ord_99831",
"preferred_agent": "Aria"
},
"knowledge_base_ids": ["kb_general", "kb_premium_perks"],
"metadata": {
"campaign_tag": "premium-support-q2",
"ab_cohort": "B"
},
"recording_enabled": true,
"max_call_duration_seconds": 600
}
Referencia de campos
| Campo | ¿Obligatorio? | Notas |
|---|---|---|
finn_id | Sí | Qué agente responde. Debe pertenecer a tu organización. |
language | No | Anula el idioma predeterminado del agente. en-US, hi-IN, es-ES, etc. |
variables | No | Conjunto libre de pares clave/valor. Disponible dentro del prompt como {variable_name}. |
knowledge_base_ids | No | Anula qué bases de conocimiento se cargan para esta llamada. |
metadata | No | Se adjunta al registro de la llamada y al webhook posterior a la llamada. Úsalo para tu propio etiquetado analítico. |
recording_enabled | No | Anula el valor predeterminado del agente. |
max_call_duration_seconds | No | Límite máximo. El valor predeterminado es 1800 (30 min). |
Rechazar la llamada
{ "action": "reject", "reason": "blocked_caller" }
Finn finaliza la llamada con un mensaje de salida configurado ("Este número ya no está en servicio"). Úsalo para aplicar la lista DNC, cuentas bloqueadas o cortes fuera de horario.
Transferir de inmediato
{ "action": "transfer", "to": "+918888888888", "reason": "vip_route" }
Omite por completo el agente de IA y dirige la llamada directamente a una persona. Útil para niveles VIP o de escalamiento.
Ejemplo de implementación (Node)
import express from "express";
const app = express();
app.use(express.json());
app.post("/finn/inbound", async (req, res) => {
const { from, to, channel } = req.body;
// 1. Look up caller in CRM (must be fast — DB index by phone)
const phone = from.phone ?? `+${from.wa_id}`;
const customer = await crm.findByPhone(phone);
// 2. Block known DNC
if (customer?.dnc) {
return res.json({ action: "reject", reason: "dnc" });
}
// 3. VIP → straight to human
if (customer?.tier === "vip") {
return res.json({
action: "transfer",
to: process.env.VIP_DESK_NUMBER!,
reason: "vip_route",
});
}
// 4. Localized agent based on caller country
const language = from.country === "IN" ? "hi-IN" : "en-US";
// 5. Pick agent + load context
return res.json({
finn_id: customer?.tier === "premium"
? "fn_premium_aria"
: "fn_standard_aria",
language,
variables: {
customer_name: customer?.name ?? "there",
account_tier: customer?.tier ?? "standard",
last_order_id: customer?.last_order_id ?? "",
},
knowledge_base_ids:
customer?.tier === "premium" ? ["kb_general", "kb_premium"] : ["kb_general"],
metadata: { ab_cohort: customer?.ab_cohort ?? "A" },
});
});
app.listen(3000);
Verificación de firma
Mismo esquema que los webhooks posteriores a la llamada: HMAC-SHA256 sobre el cuerpo sin procesar, cabecera X-Finn-Signature.
const sig = req.headers["x-finn-signature"] as string;
const ok = verifyFinnSignature(rawBody, sig, process.env.FINN_INBOUND_SECRET!);
if (!ok) return res.status(401).send("bad signature");
Consulta Webhooks posteriores a la llamada para ver la implementación completa de la verificación.
Requisitos de rendimiento
Esto está en la ruta crítica: quien llama escucha el tono de llamada mientras espera tu respuesta.
| Métrica | Objetivo |
|---|---|
| Tiempo de respuesta | < 500 ms (p99) |
| Tiempo de espera | 1000 ms |
| Alternativa al agotarse el tiempo de espera | Agente predeterminado configurado en el número de teléfono |
| Alternativa ante un error 5xx | Agente predeterminado |
| Alternativa ante JSON no válido | Agente predeterminado y error registrado |
Consejos para bajar de 500 ms
- Almacena en caché las consultas al CRM por número de teléfono durante 5 minutos
- Usa una base de datos colocalizada: webhook desde us-east y base de datos en us-west suman 80 ms por trayecto
- Precalcula las decisiones de enrutamiento: guarda la respuesta en el registro del cliente en lugar de decidirla en vivo
- Omite el enriquecimiento no esencial en el salto entrante; deja ese trabajo para el webhook posterior a la llamada
Preselección de la persona que llama
La mayoría de las aplicaciones usan el webhook entrante para uno de estos patrones:
| Patrón | Caso de uso |
|---|---|
| Búsqueda + personalización | Obtiene el registro del cliente y pasa el nombre y el nivel como variables |
| Aplicación de la lista DNC | Rechaza llamadas de números bloqueados |
| Enrutamiento VIP | Omite el agente y transfiere directamente a un operador humano |
| Detección de idioma | Elige hi-IN para India y en-US para quienes llaman desde EE. UU. |
| Pruebas A/B | Enruta el 50 % de las llamadas a una nueva versión del agente |
| Enrutamiento fuera de horario | Agente distinto (o buzón de voz) fuera del horario laboral |
| Seguimiento de campañas | Etiqueta las llamadas de números de seguimiento concretos con un ID de campaña |
| SaaS multiinquilino | Enruta al inquilino cuyo número se marcó |
Pruebas
Usa la CLI de Finn para simular llamadas entrantes sin un timbre real por PSTN:
finn inbound simulate \
--phone-number-id ph_1234 \
--from +919876543210 \
--webhook-url https://your-app.com/finn/inbound
La salida muestra la respuesta de tu endpoint, la latencia y el agente y las variables resueltos. Compatible con CI.
Problemas frecuentes
| Síntoma | Solución |
|---|---|
| Las llamadas siempre recurren al agente predeterminado | Tu endpoint tarda más de 500 ms. Revisa el registro de webhooks de Finn para ver los tiempos. |
| Quien llama oye 2-3 s de silencio antes de que hable el agente | Tu endpoint es lento pero no supera el tiempo de espera. Optimízalo a menos de 200 ms. |
| Las variables no aparecen en el habla del agente | Discrepancia entre el marcador de posición {customer_name} del prompt y la clave customerName del webhook. Usa snake_case en ambos lados. |
| Respondió el agente equivocado | Tu endpoint devolvió un finn_id que pertenece a otra organización. Verifica la propiedad. |
action: reject no se respeta | Comprueba que reason sea una cadena y que el JSON de respuesta sea válido. Las respuestas no válidas → recurren al fallback. |
Relacionado
Was this page helpful?
Still stuck or have feedback?
Email [email protected] or use the chat bubble in the bottom-right corner — it's a Finn that knows the Academy cold.