Webhooks posteriores a la llamada
Suscríbete a los eventos call.completed: transcripciones, grabaciones, resultados.
Post-call Webhooks
Dispare su servidor cada vez que termine una llamada. Recibir transcripción, grabación de URL, resultado estructurado, sentimiento y costo. El único webhook más importante para sincronizar los datos de llamada en su tubería CRM, analítica o de operaciones.
-..
Cuando se dispara
- Evento
- Fires: dentro de 5 segundos de la llamada final (cualquier razón - escogida, perdida, buzón de voz, ocupada, fallada)
- Mejor esfuerzo. Use
created_at+ controladores idempotent, no confíe en un orden estricto. - Retries: 5 intentos de más de 10 minutos en
5xx/ timeout. Último intento conectado en el registro de Webhook de dashboard.
-..
Registrar una suscripción
Via dashboard
Configuraciones → Integraciones → Webhooks → Nuevo Webhook.
Elija call.completed de la lista de eventos. Pruebe su URL de punto final. Guardar — Finn muestra el secreto de firma una vez. Recibido ahora (no puedes verlo de nuevo).
Via API
curl https://api.hirefinn.ai/v1/webhooks \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/finn/post-call",
"events": ["call.completed"],
"description": "CRM + analytics sync"
}'
La respuesta incluye secret — almacenar de forma segura.
-..
Carga
{
"id": "evt_2H4abc",
"type": "call.completed",
"created_at": "2026-05-22T14:32:14.892Z",
"org_id": "org_8fac17c5",
"data": {
"call_id": "cal_xyz789",
"deployment_id": "dep_abc123",
"finn_id": "fn_def456",
"phone_number_id": "ph_1234",
"from": "+919876543210",
"to": "+918765432100",
"call_type": "outbound",
"channel": "pstn",
"started_at": "2026-05-22T14:30:01.245Z",
"answered_at": "2026-05-22T14:30:04.812Z",
"ended_at": "2026-05-22T14:32:14.123Z",
"duration_seconds": 130,
"ring_seconds": 4,
"outcome": {
"label": "qualified",
"confidence": 0.87,
"extracted_fields": {
"preferred_slot": "2026-05-24T15:00:00+05:30",
"budget": "25000",
"is_decision_maker": true
}
},
"sentiment": "positive",
"csat_score": null,
"call_status": "completed",
"hangup_party": "agent",
"recording_url": "https://recordings.hirefinn.ai/.../cal_xyz789.mp3",
"recording_duration_seconds": 130,
"transcript_url": "https://transcripts.hirefinn.ai/.../cal_xyz789.json",
"credits_charged": 3,
"currency": "INR",
"monetary_value": 10.05,
"audience_id": "aud_xyz789",
"audience_row": {
"name": "Priya M",
"phone": "+919876543210",
"loan_amount": "500000"
},
"metadata": {
"campaign_tag": "may-cohort-3"
}
}
}
Referencia sobre el terreno
Silencio en el campo Silencio Tipo Silencio
Silencio..
TEN call_id TENIDO cadena TENIDO Globalmente único. Use como su clave dedupe
TENÍA deployment_id TENCIÓN ANTERIOR ANTERIENTE Campaña Fuente. Null for one-off test calls
Silencio from / to Silencio E.164 Silencio Pre-traducción. WhatsApp llama a usar wa_id aquí
ten channel silencio enum ten pstn whatsapp \ eterna sip ten
confidencialidad started_at Silencio ISO-8601 Cuando Finn iniciado marca / recibió la llamada
Silencio answered_at Silencio ISO-8601 / null Silencio Null si la llamada nunca se recogió
TENÍA duration_seconds TENIDO TENIDO ANTERIENTE Duración facturable, desde answered_at hasta ended_at TENIDO
Tu taxonomía de resultados personalizados desde el principio
Silencio outcome.confidence Silencio flotar 0–1 Silencio Modelo confianza en la etiqueta
TEN outcome.extracted_fields NOVEDAD objeto TENIDO Datos estructurados Per-call (forma libre por diseño rápido) TEN
ten sentiment silencio enum ten positive neutral \ eterna negative ten
ten call_status tenido en super completed no_answer\ eterna busy failed \ eterna voicemail
ten hangup_party silencio enum ten caller agent \ eterna system ten
Silencio credits_charged Silencio flotador Silencio Wallet débito para esta llamada
TEN audience_row TENIDO objeto TENIDO La fila CSV completa marcada (por salida). Null for inbound
-..
Verificación de la firma
Finn firma cada webhook con HMAC-SHA256. Verifique antes de confiar en la carga útil.
Header: X-Finn-Signature: t=1716391823,v1=abc123...
El t= es el timetamp. El v1= es HMAC-SHA256(secret, t + "." + raw_body).
Node
import crypto from "crypto";
function verifyFinnSignature(
rawBody: string,
header: string,
secret: string,
toleranceSeconds = 300,
): boolean {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string]),
);
const ts = parseInt(parts.t, 10);
const sig = parts.v1;
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - ts) > toleranceSeconds) return false; // replay guard
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Python
import hmac, hashlib, time
def verify_finn_signature(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts.get("t", 0))
sig = parts.get("v1", "")
if not ts or not sig: return False
if abs(time.time() - ts) > tolerance: return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Siempre verifique en el cuerpo de solicitud cruda, no el JSON pareado — la re-serialización cambia el espacio blanco y rompe el HMAC.
-..
Manejo de la carga útil (por ejemplo, nodo)
import express from "express";
const app = express();
// raw body required for signature verification
app.post("/finn/post-call",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.headers["x-finn-signature"] as string;
const ok = verifyFinnSignature(req.body.toString(), sig, process.env.FINN_WEBHOOK_SECRET!);
if (!ok) return res.status(401).send("bad signature");
const event = JSON.parse(req.body.toString());
// Dedupe — idempotent processing
if (await db.eventExists(event.id)) {
return res.status(200).send("dup");
}
await db.markEventSeen(event.id);
// Route by event type
if (event.type === "call.completed") {
await handleCallCompleted(event.data);
}
res.status(200).send("ok");
},
);
async function handleCallCompleted(call: any) {
// 1. Update CRM record
await crm.updateLead(call.audience_row?.phone, {
last_call_outcome: call.outcome.label,
last_call_sentiment: call.sentiment,
last_call_recording: call.recording_url,
});
// 2. If qualified, fire a Slack alert
if (call.outcome.label === "qualified") {
await slack.notify("#sales-hot-leads", `Hot lead: ${call.audience_row.name}`);
}
// 3. Push to data warehouse
await warehouse.insert("finn_calls", call);
}
-..
Fiabilidad + idempotencia
- Dedupe by
event.id- Finn puede volver a iniciar un evento que ya hemos entregado si su punto final se ha programado - Retorno 2xx en 5 segundos — de lo contrario lo tratamos como un fracaso y una reingresación
- ** Haga el trabajo pesado asinc** - colar la carga útil, ack inmediatamente
- Replay from dashboard — entregas fallidas aparecen en Settings → Integraciones → Webhooks → Logs con un botón "Replay"
-..
Compras comunes
Silencioso Silencioso
Silencio..
La Firma permanente siempre falla. Estás analizando a JSON antes de verificar. Verifica primero el cuerpo crudo
¦ Duplicate CRM filas ¦ No deduping by event.id. Añadir un único-constructor o conjunto visto
← Eventos tardíos Silencio Su punto final tomó > 5s. Mueva el procesamiento a una cola
Silencio Perdiendo audience_row Silencio La llamada era inbound (ninguna audiencia). Revisar call_type primero
tención Grabar URL 403 Silencio Grabar URLs expira después de 30 días por defecto. Espejo a su propio almacenamiento para archivo a largo plazo
-..
Eventos relacionados
El Webhook post-call es uno de varios. Ver el catálogo completo:
← Evento Silencioso
Silencio..
Silencio call.started Silencio Portador recogido
Silencio call.completed Silencio Esta página Silencio
Silencio call.transferred Silencio traslado al ser humano
Silencio deployment.completed Silencio Campaña agotada audiencia
Silencio wallet.low_balance Silencio Bajo el umbral configurado
-..
Relacionados
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.