Skip to main content

Webhooks entrants

Routage dynamique des agents — Finn appelle votre serveur lorsqu'un appel entrant arrive.

7 min read

Webhooks entrants

Lorsqu'un appel entrant arrive, Finn peut demander en temps réel à votre serveur quel agent doit répondre, quelles variables injecter et quelle base de connaissances charger. Vous pouvez ainsi router dynamiquement : selon le numéro de l'appelant, le niveau de compte, l'heure de la journée, la cohorte A/B ou toute logique gérée par votre backend.

Si un agent fixe suffit, passez cette page. Si vous souhaitez un routage dynamique par appel, c'est ici.


Fonctionnement

caller dials → Finn answers ring → Finn POSTs to your URL → your server returns agent + vars → Finn streams the voice agent

Le handshake ajoute environ 100 à 200 ms de latence. Votre endpoint doit répondre en moins de 500 ms, sans quoi Finn bascule sur l'agent par défaut.


Configurer

1. Définir l'URL du webhook entrant

Tableau de bord → Paramètres → Numéros de téléphone → [numéro] → URL du webhook entrant.

Collez votre endpoint HTTPS. Enregistrez. Finn l'appelle une fois avec un corps {"ping": true} pour vérifier qu'il est joignable.

Via l'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. Définir un agent de repli

Configurez un Finn par défaut pour ce numéro — utilisé si votre webhook dépasse le délai, renvoie une erreur ou une réponse invalide.


Charge utile de la requête

Finn envoie une requête POST à votre URL avec :

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

Pour les appels WhatsApp, le bloc from utilise des champs spécifiques à WhatsApp :

"from": {
  "wa_id": "919876543210",
  "display_name": "Priya M",
  "profile_pic_url": "https://..."
},
"channel": "whatsapp"

Réponse

Renvoyez un corps JSON en moins de 500 ms décrivant comment router l'appel :

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

Référence des champs

ChampRequis ?Remarques
finn_idOuiQuel agent répond. Doit appartenir à votre organisation.
languageNonRemplace la langue par défaut de l'agent. en-US, hi-IN, es-ES, etc.
variablesNonEnsemble libre de paires clé/valeur. Accessible dans le prompt via {variable_name}.
knowledge_base_idsNonRemplace les bases de connaissances chargées pour cet appel.
metadataNonJoint à l'enregistrement de l'appel et au webhook post-appel. À utiliser pour votre propre étiquetage analytique.
recording_enabledNonRemplace la valeur par défaut de l'agent.
max_call_duration_secondsNonLimite stricte. Valeur par défaut : 1800 (30 min).

Rejeter l'appel

{ "action": "reject", "reason": "blocked_caller" }

Finn met fin à l'appel avec un message sortant configuré (« Ce numéro n'est plus en service »). À utiliser pour l'application des listes DNC, les comptes bloqués ou les raccrochages en dehors des heures d'ouverture.

Transférer immédiatement

{ "action": "transfer", "to": "+918888888888", "reason": "vip_route" }

Contourne entièrement l'agent IA — acheminement direct vers un humain. Utile pour les niveaux VIP / escalade.


Exemple d'implémentation (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);

Vérification de la signature

Même schéma que les webhooks post-appel — HMAC-SHA256 sur le corps brut, en-tête 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");

Voir Webhooks post-appel pour l'implémentation complète de la vérification.


Exigences de performance

Ceci se situe sur le chemin critique — l'appelant entend la tonalité pendant qu'il attend votre réponse.

MétriqueCible
Temps de réponse< 500 ms (p99)
Délai d'expiration1000 ms
Repli en cas d'expirationAgent par défaut configuré sur le numéro de téléphone
Repli en cas d'erreur 5xxAgent par défaut
Repli en cas de JSON invalideAgent par défaut + erreur journalisée

Conseils pour rester sous 500 ms

  • Mettez en cache les recherches CRM par numéro de téléphone pendant 5 minutes
  • Utilisez une base de données colocalisée — webhook depuis us-east, base de données dans us-west = 80 ms dans chaque sens
  • Pré-calculez les décisions d'acheminement — enregistrez la réponse sur la fiche client, ne décidez pas en direct
  • Ignorez l'enrichissement non essentiel lors du saut entrant — reportez ce travail sur le webhook post-appel

Présélection des appelants

La plupart des applications utilisent le webhook entrant pour l'un de ces modèles :

ModèleCas d'usage
Recherche + personnalisationRécupérer la fiche client, transmettre le nom et le niveau comme variables
Application de la liste rougeRejeter les appels provenant de numéros bloqués
Routage VIPIgnorer l'agent, transférer directement vers un conseiller humain
Détection de la langueChoisir hi-IN pour l'Inde, en-US pour les appelants américains
Tests A/BRouter 50 % des appels vers une nouvelle version de l'agent
Routage hors horairesAgent différent (ou messagerie vocale) en dehors des heures d'ouverture
Suivi de campagneAssocier un ID de campagne aux appels provenant de numéros de suivi spécifiques
SaaS multi-locataireRouter vers le locataire dont le numéro a été composé

Tests

Utilisez le CLI Finn pour simuler des appels entrants sans sonnerie PSTN réelle :

finn inbound simulate \
  --phone-number-id ph_1234 \
  --from +919876543210 \
  --webhook-url https://your-app.com/finn/inbound

La sortie affiche la réponse de votre endpoint, la latence, ainsi que l'agent et les variables résolus. Compatible CI.


Pièges courants

SymptômeCorrectif
Les appels basculent toujours vers l'agent par défautVotre endpoint dépasse 500 ms. Vérifiez les temps de réponse dans le journal des webhooks de Finn.
L'appelant entend 2 à 3 s de silence avant que l'agent ne parleVotre endpoint est lent mais reste sous le délai d'expiration. Optimisez-le à moins de 200 ms.
Les variables n'apparaissent pas dans les propos de l'agentDécalage entre l'espace réservé {customer_name} du prompt et la clé customerName du webhook. Utilisez le snake_case des deux côtés.
Le mauvais agent a réponduVotre endpoint a renvoyé un finn_id appartenant à une autre organisation. Vérifiez la propriété.
action: reject non pris en compteVérifiez que reason est une chaîne et que le JSON de réponse est valide. Réponses invalides → repli.

Ressources associées

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.