Skip to main content
ai voice11 mars 202620 min de lecture

Comment créer un réceptionniste vocal IA avec Vapi.ai

Guide étape par étape pour construire un réceptionniste vocal IA prêt pour la production, disponible 24h/24. Étude de cas réelle d'une entreprise de plomberie qui ne manque plus aucun prospect.

Loic Bachellerie

Senior Product Engineer

Le problème à l'origine de tout

Un patron d'entreprise de plomberie m'a appelé, frustré. Sa société manquait 40 % des appels entrants. Les urgences tombaient sur la messagerie à 2 h du matin. Les clients potentiels appelaient ses concurrents à la place. Il avait une réceptionniste à temps plein pendant les heures ouvrées, mais rien en dehors de ce créneau. Il perdait de l'argent réel, chaque nuit.

Trois heures après notre premier échange, Captain Plumber disposait d'un réceptionniste vocal IA entièrement déployé, construit sur Vapi.ai. Il gère désormais plus de 200 appels par mois, redirige les urgences vers un plombier d'astreinte en moins de 90 secondes, enregistre automatiquement les données des prospects dans un CRM, et a réduit les appels manqués de 40 % à moins de 2 %.

Ce guide est le tutoriel complet. Je vous montrerai l'architecture, la configuration Vapi.ai, le backend TypeScript, la gestion des cas limites et le déploiement en production — tout ce que j'ai réellement utilisé. À la fin, vous aurez un réceptionniste vocal IA fonctionnel que vous pourrez déployer pour n'importe quelle entreprise de services.


Pourquoi l'IA vocale est l'automatisation au meilleur ROI pour les entreprises de services

Avant d'écrire la moindre ligne de code, il est utile de comprendre pourquoi la voix compte autant. La plupart des projets d'automatisation sur lesquels je travaille relèvent du « nice to have ». Les agents vocaux sont différents — ils interviennent exactement au moment où l'entreprise gagne ou perd un client.

Quand quelqu'un appelle un plombier à 23 h avec un tuyau qui a éclaté, il ne va pas essayer un deuxième numéro si personne ne décroche. Il relance immédiatement une recherche. L'entreprise qui répond décroche le contrat.

Voici comment l'IA vocale se compare aux alternatives qu'utilisent la plupart des petites entreprises :

OptionDisponibilitéCoût/moisCapture de leadsPersonnalisation
Réceptionniste humainHeures ouvrées2 500-4 000 $ManuelleÉlevée
Messagerie vocale24h/24~0 $Souvent abandonnéeAucune
Centre d'appels24h/24800-2 000 $InconsistanteFaible
Agent vocal IA24h/2450-200 $AutomatiséeConfigurable

L'agent vocal IA n'est pas juste moins cher. Il est structurellement meilleur pour la tâche : il ne passe jamais une mauvaise journée, il suit toujours le script, il capture les mêmes champs à chaque fois, et il route correctement à toute heure.

Pour Captain Plumber, le calcul du ROI était simple. Un seul appel d'urgence capturé à 400-800 $ couvre l'intégralité du coût mensuel du système IA.


Vue d'ensemble de l'architecture

Un réceptionniste vocal IA construit sur Vapi.ai comporte quatre couches. Les comprendre toutes les quatre avant de commencer à construire vous épargnera des heures de débogage.

Réceptionniste vocal IA - Architecture du système

Comment un appel transite de l'appelant jusqu'à votre CRM

1

Couche téléphonique - Twilio

L'appelant compose votre numéro. Twilio route l'appel vers Vapi via un webhook. Vous possédez le numéro et contrôlez les règles de routage.

2

Traitement vocal - Vapi.ai

Vapi gère le Speech-to-Text (Deepgram), l'orchestration LLM (GPT-4 ou Claude), et le Text-to-Speech (ElevenLabs). Latence inférieure à 1 seconde.

3

Logique métier - Votre backend

Une API Next.js ou Express reçoit les appels de fonctions de Vapi. Gère la sauvegarde des leads, le routage d'urgence, la prise de rendez-vous et les confirmations SMS.

4

Couche données - CRM / Base de données

Chaque appel produit des données structurées : nom de l'appelant, adresse, type de problème, urgence, créneau de rappel souhaité. Synchronisé avec Airtable, HubSpot ou Supabase.

TwilioVapi.aiDeepgram STTOpenAI GPT-4ElevenLabs TTSAirtable CRM

Le point clé : Vapi gère la complexité qui prendrait des mois à développer soi-même — détection des tours de parole, gestion des interruptions, optimisation de la latence et basculement entre fournisseurs. Votre travail consiste à configurer le comportement de l'agent et à écrire les endpoints de logique métier.


Prérequis et stack technique

Voici tout ce dont vous avez besoin avant de commencer :

  • Compte Vapi.ai — le tier gratuit vous donne 10 $ de crédits, suffisant pour tester en profondeur
  • Compte Twilio — pour un numéro de téléphone dédié (~1 $/mois)
  • Clé API OpenAI — GPT-4o offre le meilleur rapport latence/qualité en 2026
  • Node.js 18+ avec TypeScript
  • Un backend déployé — Vercel, Railway ou Render fonctionnent très bien

Temps de mise en place estimé : 3 à 4 heures pour votre premier déploiement en production.


Étape 1 - Configuration des comptes et premier assistant

Installer le SDK

npm install @vapi-ai/server-sdk @vapi-ai/web

Créer votre premier assistant via l'API

Je préfère créer les assistants de manière programmatique plutôt que via le tableau de bord. Cela rend votre configuration versionnée et reproductible d'un environnement à l'autre.

// lib/vapi/create-assistant.ts
import { VapiClient } from "@vapi-ai/server-sdk";
 
const client = new VapiClient({ token: process.env.VAPI_API_KEY! });
 
export async function createPlumbingReceptionist() {
  const assistant = await client.assistants.create({
    name: "Captain Plumber - AI Receptionist",
    model: {
      provider: "openai",
      model: "gpt-4o",
      temperature: 0.4,
      systemPrompt: buildSystemPrompt(),
    },
    voice: {
      provider: "elevenlabs",
      voiceId: "21m00Tcm4TlvDq8ikWAM", // Rachel - warm, professional
      stability: 0.5,
      similarityBoost: 0.75,
    },
    firstMessage:
      "Thanks for calling Captain Plumber. I'm here to help. Can I get your name and tell me what's going on with your plumbing?",
    endCallMessage:
      "You're all set. A member of our team will be in touch shortly. Have a good one.",
    recordingEnabled: true,
    transcriptPlan: {
      enabled: true,
    },
    silenceTimeoutSeconds: 30,
    maxDurationSeconds: 600,
    serverUrl: process.env.VAPI_WEBHOOK_URL!, // your backend endpoint
  });
 
  console.log("Assistant created:", assistant.id);
  return assistant;
}
 
function buildSystemPrompt(): string {
  return `You are the AI receptionist for Captain Plumber, a licensed plumbing company serving the Greater Boston area.
 
## Your Role
You handle inbound calls to collect information, assess urgency, and route the caller appropriately. You are professional, warm, and efficient. You do not diagnose problems - you gather information and connect people to the right help.
 
## Information to Collect on Every Call
1. Caller full name
2. Service address (street, city)
3. Brief description of the issue
4. Best callback number (confirm if different from caller ID)
5. Whether the situation is urgent or can wait
 
## Emergency Classification
A call is an EMERGENCY if the caller mentions:
- Active water leak or flooding
- No hot water (in winter months)
- Sewage backup
- Gas smell (redirect to 911 first, then collect info)
- Pipe burst
 
## Conversation Rules
- Keep responses under 25 words when possible - this is a phone call
- Repeat back key details to confirm accuracy
- Never say "I don't know" - say "Let me make sure someone calls you about that"
- If a caller is distressed, acknowledge it before asking questions
- Confirm the callback number by reading it back digit by digit
 
## Routing Logic
- EMERGENCY: Tell them a plumber will call within 15 minutes. Trigger the emergency function immediately.
- NON-EMERGENCY: Offer available appointment slots for the next business day. Trigger the schedule function.
- INFORMATION ONLY: Answer basic questions from the FAQ, then offer to book an estimate.
 
## What You Never Do
- Quote prices over the phone
- Guarantee specific arrival times beyond the 15-minute emergency callback
- Collect payment information
- Make promises outside your defined scope`;
}

Pourquoi une température de 0.4 ?

Pour un réceptionniste, vous voulez de la constance plutôt que de la créativité. Une température basse (0.3-0.5) signifie que le modèle suit de près ses instructions. J'ai vu des agents avec une température à 0.8+ commencer à improviser des réponses en contradiction avec les règles métier. Pour les contextes professionnels, gardez-la basse.


Étape 2 - Configuration du function calling

Le function calling est ce qui différencie une démo jouet d'un système de production. Sans lui, votre agent ne peut que parler — il ne peut rien faire concrètement. Les fonctions permettent au LLM de déclencher de vraies actions dans vos systèmes pendant l'appel.

Définir vos fonctions dans l'assistant

// lib/vapi/assistant-functions.ts
 
export const vapiTools = [
  {
    type: "function" as const,
    function: {
      name: "capture_lead",
      description:
        "Save the caller's information as a new lead in the CRM. Call this once you have collected name, address, issue, and callback number.",
      parameters: {
        type: "object",
        properties: {
          callerName: {
            type: "string",
            description: "Full name of the caller",
          },
          callbackPhone: {
            type: "string",
            description: "Phone number to call back, digits only",
          },
          serviceAddress: {
            type: "string",
            description: "Full service address including city",
          },
          issueDescription: {
            type: "string",
            description: "Brief description of the plumbing issue in the caller's own words",
          },
          isEmergency: {
            type: "boolean",
            description: "True if the caller described an active leak, flooding, or similar emergency",
          },
          preferredTime: {
            type: "string",
            description: "Preferred appointment window if not an emergency, e.g. 'tomorrow morning'",
          },
        },
        required: ["callerName", "callbackPhone", "serviceAddress", "issueDescription", "isEmergency"],
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "route_emergency",
      description:
        "Immediately notify the on-call plumber via SMS and paging. Only call this when isEmergency is true.",
      parameters: {
        type: "object",
        properties: {
          callerName: { type: "string" },
          callbackPhone: { type: "string" },
          serviceAddress: { type: "string" },
          issueDescription: { type: "string" },
        },
        required: ["callerName", "callbackPhone", "serviceAddress", "issueDescription"],
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "check_availability",
      description:
        "Check available appointment slots for the next 3 business days. Call this for non-emergency scheduling.",
      parameters: {
        type: "object",
        properties: {
          preferredDate: {
            type: "string",
            description: "Caller's preferred date if mentioned, e.g. 'tomorrow' or 'Thursday'",
          },
          timePreference: {
            type: "string",
            enum: ["morning", "afternoon", "any"],
            description: "Caller's time preference",
          },
        },
        required: [],
      },
    },
  },
];

Mettre à jour l'assistant pour inclure les fonctions

// Add tools to the assistant create call
const assistant = await client.assistants.create({
  name: "Captain Plumber - AI Receptionist",
  model: {
    provider: "openai",
    model: "gpt-4o",
    temperature: 0.4,
    systemPrompt: buildSystemPrompt(),
    tools: vapiTools, // attach the functions here
  },
  // ... rest of config
});

Étape 3 - Construction du gestionnaire de webhooks

Quand le LLM décide d'appeler une fonction, Vapi envoie une requête POST à votre serverUrl. C'est là que réside votre logique métier.

// app/api/vapi/webhook/route.ts  (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";
import { captureLeadInCRM } from "@/lib/crm/capture-lead";
import { routeEmergency } from "@/lib/notifications/route-emergency";
import { getAvailableSlots } from "@/lib/calendar/availability";
 
interface VapiFunctionCallPayload {
  message: {
    type: "function-call";
    functionCall: {
      name: string;
      parameters: Record<string, unknown>;
    };
    call: {
      id: string;
      phoneNumber?: { number: string };
    };
  };
}
 
export async function POST(req: NextRequest): Promise<NextResponse> {
  const body = (await req.json()) as VapiFunctionCallPayload;
  const { message } = body;
 
  if (message.type !== "function-call") {
    return NextResponse.json({ result: "ok" });
  }
 
  const { name, parameters } = message.functionCall;
  const callId = message.call.id;
 
  try {
    switch (name) {
      case "capture_lead":
        return await handleCaptureLead(parameters, callId);
 
      case "route_emergency":
        return await handleRouteEmergency(parameters, callId);
 
      case "check_availability":
        return await handleCheckAvailability(parameters);
 
      default:
        console.warn(`Unknown function called: ${name}`);
        return NextResponse.json({
          result: "Function not recognized. Apologize to the caller and offer a callback.",
        });
    }
  } catch (error) {
    console.error(`Webhook handler error for ${name}:`, error);
    // Return a graceful message - the agent will speak this to the caller
    return NextResponse.json({
      result: "There was a technical issue on our end. Please tell the caller a team member will call them back within the hour.",
    });
  }
}
 
async function handleCaptureLead(
  params: Record<string, unknown>,
  callId: string
): Promise<NextResponse> {
  const lead = {
    callerName: params.callerName as string,
    callbackPhone: params.callbackPhone as string,
    serviceAddress: params.serviceAddress as string,
    issueDescription: params.issueDescription as string,
    isEmergency: params.isEmergency as boolean,
    preferredTime: (params.preferredTime as string) ?? null,
    callId,
    source: "ai-receptionist",
    createdAt: new Date().toISOString(),
  };
 
  const savedLead = await captureLeadInCRM(lead);
 
  return NextResponse.json({
    result: `Lead saved successfully. Reference number ${savedLead.id}. Tell the caller their information has been recorded and confirm the callback number you collected.`,
  });
}
 
async function handleRouteEmergency(
  params: Record<string, unknown>,
  callId: string
): Promise<NextResponse> {
  await routeEmergency({
    callerName: params.callerName as string,
    callbackPhone: params.callbackPhone as string,
    serviceAddress: params.serviceAddress as string,
    issueDescription: params.issueDescription as string,
    callId,
  });
 
  return NextResponse.json({
    result: "Emergency alert sent. Tell the caller: an on-call plumber has been notified and will call them back within 15 minutes. Give them a confirmation.",
  });
}
 
async function handleCheckAvailability(
  params: Record<string, unknown>
): Promise<NextResponse> {
  const slots = await getAvailableSlots({
    preferredDate: params.preferredDate as string | undefined,
    timePreference: (params.timePreference as "morning" | "afternoon" | "any") ?? "any",
  });
 
  if (slots.length === 0) {
    return NextResponse.json({
      result: "No slots available this week. Offer the caller the first available slot next week or take their info for a callback when slots open.",
    });
  }
 
  const slotDescriptions = slots
    .slice(0, 3)
    .map((s) => s.label)
    .join(", ");
 
  return NextResponse.json({
    result: `Available slots: ${slotDescriptions}. Read these options to the caller and ask which works best.`,
  });
}

Le pattern clé : renvoyer des instructions, pas juste des données

Remarquez que chaque fonction renvoie une chaîne result formulée comme une instruction pour l'agent. Le LLM lit ce résultat et l'utilise pour formuler sa réponse orale. Cela vous donne un contrôle précis sur ce que l'agent dit après chaque action, sans avoir à mettre à jour le prompt système en permanence.


Étape 4 - Logique de routage d'urgence

C'est la partie qui a eu le plus grand impact métier pour Captain Plumber. Quand quelqu'un appelle à 2 h du matin avec un tuyau qui a éclaté, le système doit alerter un humain immédiatement.

// lib/notifications/route-emergency.ts
import twilio from "twilio";
 
const twilioClient = twilio(
  process.env.TWILIO_ACCOUNT_SID!,
  process.env.TWILIO_AUTH_TOKEN!
);
 
interface EmergencyAlert {
  callerName: string;
  callbackPhone: string;
  serviceAddress: string;
  issueDescription: string;
  callId: string;
}
 
export async function routeEmergency(alert: EmergencyAlert): Promise<void> {
  const oncallNumbers = getOnCallNumbers(); // rotate through your on-call schedule
  const messageBody = buildEmergencyMessage(alert);
 
  // SMS all on-call staff simultaneously
  const smsPromises = oncallNumbers.map((number) =>
    twilioClient.messages.create({
      body: messageBody,
      from: process.env.TWILIO_FROM_NUMBER!,
      to: number,
    })
  );
 
  // Also call the primary on-call number - SMS can be missed
  const callPromise = twilioClient.calls.create({
    twiml: buildEmergencyTwiML(alert),
    from: process.env.TWILIO_FROM_NUMBER!,
    to: oncallNumbers[0],
  });
 
  await Promise.all([...smsPromises, callPromise]);
 
  // Log for audit trail
  console.log(`Emergency routed for call ${alert.callId}`, {
    caller: alert.callerName,
    address: alert.serviceAddress,
    notifiedNumbers: oncallNumbers.length,
  });
}
 
function buildEmergencyMessage(alert: EmergencyAlert): string {
  return [
    "EMERGENCY CALL - Captain Plumber",
    `Caller: ${alert.callerName}`,
    `Phone: ${alert.callbackPhone}`,
    `Address: ${alert.serviceAddress}`,
    `Issue: ${alert.issueDescription}`,
    "Please call back within 15 min.",
  ].join("\n");
}
 
function buildEmergencyTwiML(alert: EmergencyAlert): string {
  return `<Response>
    <Say voice="Polly.Joanna">
      Emergency plumbing call. Caller ${alert.callerName} at ${alert.serviceAddress}
      reports ${alert.issueDescription}.
      Call back ${alert.callbackPhone} within 15 minutes.
      Press 1 to acknowledge.
    </Say>
    <Gather numDigits="1" />
  </Response>`;
}
 
function getOnCallNumbers(): string[] {
  // In production, pull this from your scheduling system or a simple env var
  const raw = process.env.ONCALL_NUMBERS ?? "";
  return raw.split(",").filter(Boolean);
}

Étape 5 - Intégration CRM

Chaque appel doit produire un enregistrement propre. J'ai utilisé Airtable pour Captain Plumber parce que le propriétaire l'utilisait déjà pour le suivi des chantiers, mais le pattern fonctionne avec n'importe quel CRM.

// lib/crm/capture-lead.ts
import Airtable from "airtable";
 
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
  process.env.AIRTABLE_BASE_ID!
);
 
interface LeadRecord {
  callerName: string;
  callbackPhone: string;
  serviceAddress: string;
  issueDescription: string;
  isEmergency: boolean;
  preferredTime: string | null;
  callId: string;
  source: string;
  createdAt: string;
}
 
interface SavedLead {
  id: string;
}
 
export async function captureLeadInCRM(lead: LeadRecord): Promise<SavedLead> {
  const record = await base("Leads").create({
    Name: lead.callerName,
    Phone: lead.callbackPhone,
    Address: lead.serviceAddress,
    Issue: lead.issueDescription,
    Emergency: lead.isEmergency,
    "Preferred Time": lead.preferredTime ?? "Flexible",
    "Call ID": lead.callId,
    Source: lead.source,
    Status: lead.isEmergency ? "Emergency - Needs Immediate Follow-up" : "New Lead",
    "Created At": lead.createdAt,
  });
 
  return { id: record.id };
}

Confirmation SMS à l'appelant

Après la sauvegarde du lead, envoyez une confirmation à l'appelant. Cette simple étape a significativement amélioré la satisfaction post-appel chez Captain Plumber — les appelants ont l'impression d'avoir eu affaire à une vraie entreprise, pas à un robot.

// lib/notifications/send-caller-confirmation.ts
import twilio from "twilio";
 
const client = twilio(
  process.env.TWILIO_ACCOUNT_SID!,
  process.env.TWILIO_AUTH_TOKEN!
);
 
export async function sendCallerConfirmation(
  phone: string,
  callerName: string,
  isEmergency: boolean
): Promise<void> {
  const message = isEmergency
    ? `Hi ${callerName}, this is Captain Plumber. An on-call plumber has been notified and will call you within 15 minutes. Save this number: ${process.env.BUSINESS_PHONE}.`
    : `Hi ${callerName}, thanks for calling Captain Plumber. We've got your info and will be in touch to confirm your appointment. Questions? Call or text ${process.env.BUSINESS_PHONE}.`;
 
  await client.messages.create({
    body: message,
    from: process.env.TWILIO_FROM_NUMBER!,
    to: phone,
  });
}

Étape 6 - Gestion des cas limites

Les agents vocaux en production échouent de manière prévisible. Voici les cas limites que j'ai gérés pour Captain Plumber, et comment.

L'appelant ne parle pas

Si quelqu'un appelle et reste silencieux, l'agent doit gérer la situation avec élégance plutôt que de raccrocher brusquement.

// Add to assistant config
silenceTimeoutSeconds: 10,
// Vapi will end the call after 10s of silence
// Set firstMessage to prompt immediately so callers know someone is there
firstMessage: "Thanks for calling Captain Plumber. Can you hear me okay? Take your time and let me know what you need.",

L'appelant veut parler à un humain

Ajoutez ceci à votre prompt système :

If the caller explicitly asks to speak with a person, say: "Absolutely, I understand. Let me make sure I get your details first so our team can call you back with everything they need. What's the best number to reach you?"

Do not argue. Collect the info, then tell them: "Perfect, someone from our team will call you back shortly. We appreciate your patience."

Cela permet de conserver la capture de lead même quand l'appelant refuse l'interaction avec l'IA — ce qui est le véritable objectif.

Audio flou ou incompréhensible

If you cannot understand what the caller said, ask once for clarification: "Sorry, I missed that - could you repeat that for me?"

If you still cannot understand after a second attempt, say: "Let me make sure our team reaches out directly. Can I get your callback number?"

Basculez élégamment vers la capture de lead. Toujours.

Coupure ou déconnexion de l'appel

Configurez Vapi pour gérer les événements de fin d'appel et sauvegarder les données partielles collectées :

// app/api/vapi/call-end/route.ts
import { NextRequest, NextResponse } from "next/server";
 
interface CallEndPayload {
  message: {
    type: "end-of-call-report";
    call: { id: string };
    transcript: string;
    summary: string;
  };
}
 
export async function POST(req: NextRequest): Promise<NextResponse> {
  const body = (await req.json()) as CallEndPayload;
 
  if (body.message.type !== "end-of-call-report") {
    return NextResponse.json({ ok: true });
  }
 
  const { call, transcript, summary } = body.message;
 
  // Save the full transcript and summary to your database
  // Even if the lead capture function was never triggered, you have the transcript
  await saveCallRecord({
    callId: call.id,
    transcript,
    summary,
    endedAt: new Date().toISOString(),
  });
 
  return NextResponse.json({ ok: true });
}

Vapi envoie un rapport de fin d'appel avec la transcription complète et un résumé généré. Pour Captain Plumber, cela s'est avéré inestimable — quand un appel est coupé en cours de route, le propriétaire peut consulter la transcription et relancer manuellement.


Étape 7 - Connexion de votre numéro Twilio

Une fois votre assistant créé et votre webhook en ligne, connectez un numéro de téléphone :

// lib/vapi/connect-phone-number.ts
import { VapiClient } from "@vapi-ai/server-sdk";
 
const client = new VapiClient({ token: process.env.VAPI_API_KEY! });
 
export async function connectPhoneNumber(
  twilioPhoneNumber: string,
  assistantId: string
): Promise<void> {
  await client.phoneNumbers.create({
    number: twilioPhoneNumber,
    twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,
    twilioAuthToken: process.env.TWILIO_AUTH_TOKEN!,
    assistantId,
    name: "Captain Plumber Main Line",
  });
 
  console.log(`Phone number ${twilioPhoneNumber} connected to assistant ${assistantId}`);
}

Appelez cette fonction une seule fois lors de la configuration. Après cela, chaque appel vers ce numéro Twilio est directement dirigé vers votre réceptionniste IA.

Routage de secours

Pour Captain Plumber, j'ai configuré un plan de secours pour que si Vapi est injoignable (rare mais possible), Twilio bascule vers un message enregistré avec le numéro de portable du propriétaire. Prévoyez toujours un plan de secours.

// Twilio Webhook fallback TwiML (paste in your Twilio console)
<Response>
  <Say>
    Thanks for calling Captain Plumber. We're experiencing a brief technical issue.
    Please call back at 617-555-0100 or leave a message and we'll return your call shortly.
  </Say>
  <Record maxLength="60" />
</Response>

Captain Plumber - Résultats en conditions réelles

Voici ce que 90 jours de données de production ont donné pour ce déploiement spécifique.

Captain Plumber - Résultats sur 90 jours

Avant et après le déploiement du réceptionniste vocal IA

98%

Appels décrochés

Avant : 60%

217

Appels traités/mois

Moy. 7,2 par jour

34

Urgences routées/mois

Temps de réponse moy. : 11 min

$127

Coût mensuel IA

vs 3 200 $ réceptionniste

Qualité des leads

100 % des leads capturés avec nom, adresse, description du problème et numéro de rappel — contre environ 60 % de complétude pour les messages vocaux.

Chiffre d'affaires hors horaires

Les interventions d'urgence réservées entre 21 h et 7 h ont été multipliées par 3 dès le premier mois. Le propriétaire estime que cela seul couvre plus de 15 fois le coût mensuel de l'IA.

Un chiffre qui m'a surpris : 34 % de tous les appels arrivaient en dehors des heures ouvrées. Avant le réceptionniste IA, ces appels tombaient sur la messagerie. La plupart n'étaient jamais rappelés parce que les gens avaient déjà trouvé un autre plombier.


Checklist de déploiement en production

Avant la mise en ligne, passez en revue chaque point de cette liste :

Checklist pré-lancement

Effectuer 20+ appels de test couvrant les flux normaux, les cas limites et les urgences
Vérifier que le endpoint webhook est accessible en HTTPS avec un certificat SSL valide
Confirmer que les SMS d'urgence atteignent tous les numéros d'astreinte en moins de 30 secondes
Vérifier que les enregistrements CRM sont créés correctement avec tous les champs requis
Tester le plan de secours Twilio en pointant temporairement serverUrl vers un endpoint inexistant
Mettre en place des alertes de monitoring pour les erreurs webhook et les appels de fonctions échoués
Passer en revue les enregistrements des appels de test avec le propriétaire de l'entreprise
Ajouter un rate limiting sur votre endpoint webhook (Vapi envoie des retries en cas de timeout)
Définir des limites de dépenses dans les tableaux de bord Vapi et OpenAI pour éviter les coûts incontrôlés

Ventilation des coûts à grande échelle

Comprendre le modèle de coûts est important avant de proposer ceci à un client ou de vous engager dans un déploiement.

ComposantCoût unitaire200 appels/mois (3 min moy.)1 000 appels/mois
Plateforme Vapi~0,05 $/min30 $150 $
OpenAI GPT-4o~0,01 $/min equiv.6 $30 $
ElevenLabs TTS~0,003 $/min1,80 $9 $
Twilio (appels)0,013 $/min7,80 $39 $
Twilio (SMS)0,0079 $/msg1,58 $ (200 SMS)7,90 $
Total~47 $/mois~236 $/mois

À 200 appels par mois, le coût tout compris est d'environ 50 $. Un seul appel d'urgence converti à 500 $ donne un retour de 10x dès le premier mois. Quand le volume augmente, vous pouvez optimiser vers GPT-4o-mini pour les interactions plus simples et réduire significativement les coûts sans perte de qualité perceptible.


Conseils pour l'ingénierie du prompt système

Le prompt système est à l'origine de la plupart des problèmes en production. Voici les patterns qui ont fait la plus grande différence chez Captain Plumber :

1. Définissez le persona dès les deux premières phrases. N'enterrez pas la description du rôle. La première priorité du LLM doit être claire immédiatement.

2. Utilisez des listes numérotées pour les règles, pas des paragraphes. Quand le modèle doit suivre 10 règles, les listes numérotées sont analysées plus fiablement que la prose continue.

3. Incluez explicitement les anti-patterns. L'instruction « ne citez pas de prix » est plus fiable que d'espérer que le modèle le déduise. Dites exactement ce que vous ne voulez pas.

4. Gardez le prompt sous 1 500 tokens. Les prompts plus longs diluent la concentration. Si votre prompt s'allonge, c'est généralement que votre agent essaie de faire trop de choses. Divisez les responsabilités.

5. Versionnez vos prompts. Stockez-les dans votre codebase comme des constantes, pas dans le tableau de bord Vapi. Quand quelque chose casse à 3 h du matin, vous voulez un historique git, pas un mystère.


Ce que ce système ne fait pas

Je tiens à être direct sur les limitations pour que vous fixiez des attentes réalistes avec vos clients.

Le réceptionniste IA excelle dans la collecte structurée d'informations et le routage. Il ne négocie pas les prix, ne gère pas la planification complexe entre plusieurs techniciens, et ne gère pas les appelants hostiles qui passent à un langage abusif (vous devriez ajouter des chemins d'escalade explicites pour cela).

Pour Captain Plumber, nous avons établi un garde-fou clair : tout appelant qui demandait un technicien spécifique par son nom, ou qui haussait le ton, était immédiatement redirigé vers un message vocal avec transcription et un rappel humain garanti sous 2 heures. L'IA n'est pas le bon outil pour chaque situation — savoir quand passer la main fait partie d'une bonne conception de système.


Prochaines étapes

Si vous souhaitez aller plus loin avec cette configuration :

  • Ajouter une analyse de sentiment sur la transcription de l'appel pour signaler les appelants mécontents en vue d'un suivi prioritaire
  • Connecter Google Calendar au lieu d'une gestion de créneaux personnalisée pour une disponibilité en temps réel
  • Construire un tableau de bord avec l'API d'analytics d'appels de Vapi pour que le propriétaire puisse consulter les résumés d'appels sans écouter les enregistrements
  • Tester plusieurs voix en A/B — ElevenLabs propose des dizaines d'options vocales, et la voix idéale pour un cabinet médical est différente de celle pour un plombier
  • Intégrer Google My Business pour récupérer le contexte de l'appelant avant le début de la conversation

L'architecture de ce guide est la même fondation que j'utilise pour toutes ces extensions. Une fois le gestionnaire de webhooks et le pattern de function calling en place, ajouter des fonctionnalités est simple.


Résumé

Construire un réceptionniste vocal IA avec Vapi.ai se résume à cinq choses bien faites :

  1. Un prompt système clair et contraint qui définit le persona et les garde-fous
  2. Du function calling qui connecte l'agent à vos systèmes réels
  3. Un gestionnaire de webhooks qui renvoie des instructions actionnables, pas juste des données
  4. Une gestion rigoureuse des cas limites pour le silence, les escalades et les appels coupés
  5. Un chemin de routage d'urgence qui fonctionne à 2 h du matin sans faillir

Pour Captain Plumber, le projet est passé du concept à la production en une seule session de travail. L'entreprise répond désormais à 98 % des appels, capture chaque lead avec des informations complètes, et route les urgences en moins de 90 secondes — 24h/24, pour moins de 130 $ par mois.

L'IA vocale n'est pas le futur du service client pour les petites entreprises. Elle est disponible dès maintenant, et les entreprises qui la déploient aujourd'hui construisent un avantage structurel sur leurs concurrents qui envoient encore leurs appels sur messagerie.


Besoin d'aide pour construire un réceptionniste vocal IA pour votre entreprise ? Je construis ces systèmes de bout en bout — de la configuration Vapi à l'intégration CRM jusqu'au déploiement en production. Contactez-moi et nous pourrons discuter de la configuration idéale pour votre cas d'usage.

Share:

Recevez des perspectives d'ingénierie pratiques

Agents vocaux IA, workflows d'automatisation et livraison rapide. Pas de spam, désabonnement à tout moment.