Skip to main content

Inbound Call Agent

Audience: Sales · Management · QA · Support · Engineering · Where in app: Settings → AI Agents → Inbound Call Agent (per-agent tabs: AI Engine, Behavior, Phone Numbers, Call Forwarding, Working Hours, Post-call Tags, Webhook) · Plan availability: Per-company admin gate ai_inbound_call_allowed — see Plan & limits

The Inbound Call Agent answers your incoming phone calls with an AI voice that sounds like a professional human rep — greeting the caller, answering questions about your business, qualifying the lead, booking a meeting if there's interest, and transferring to a human when asked. It runs on real phone numbers (Twilio, imported into the platform) and is deployed to ElevenLabs Conversational AI. A company can run multiple inbound agents, each with its own prompt, voice, language, working-hours rules, phone numbers, and post-call webhook. The reserved agent_key default maps to the legacy company.ai_inbound_call object; additional agents are slots in company.ai_inbound_call_agents.

What it does

  • Answers inbound phone calls with a configurable AI voice persona (name, instructions, greeting), auto-switching among ~70 preset languages (language_detection; the configured language is the greeting).
  • Picks the voice (from the ElevenLabs voice catalog, with audio previews and gender/accent/language filters), the agent language, and the AI model (curated inner-LLM list).
  • Rings real phones first via Call Forwarding (routing_priorities) — up to 10 numbers in order, each with its own ring timeout, falling through to the AI.
  • Forwards to a human on request via a per-agent human-handoff number (falls back to the company number).
  • Optionally transfers in-hours calls straight to a human via a per-day Working Hours schedule (outside hours, or when disabled, the AI answers).
  • Buys numbers in-app (US/CA/GB/AU, local/toll-free/mobile, paid from the telephony wallet) or imports BYO Twilio and Telnyx numbers so incoming calls route to the AI.
  • Tags the GHL contact after the call (post_call_tags: built-ins booked/qualified + AI-matched custom rules).
  • POSTs a post-call webhook (transcript, transcript_text, call_sentiment, signed recording_url, phone numbers, direction, duration) to your URL when a call finishes.
  • Books meetings, looks up products, transfers, sends follow-ups, and (for trade companies) can log a Simpro job via voice tools — see Call Agent and How it works.

How it works

  1. Create an agent. From the agent list (AgentList.vue) you create an agent with a name (POST /ai-inbound-agents). The default agent always exists; extras are additional slots. Agents can be cloned.
  2. Configure across the tabs (each save goes through PATCH /ai-inbound-agents/:agent_key):
    • AI Engine — voice + language + AI model.
    • Behavior — agent name, greeting/opening script, and agent instructions.
    • Phone Numbers — the human-handoff number + in-app number purchase + imported Twilio/Telnyx numbers.
    • Call Forwarding — ordered ring-first numbers (routing_priorities).
    • Working Hours — optional in-hours human transfer.
    • Post-call Tags — GHL outcome tagging rules.
    • Webhook — optional post-call POST.
  3. Deploy to ElevenLabs. Saving Behavior, AI Engine, or the handoff number triggers useInboundAgentSyncPOST {aiUrl}/elevenlabs/sync-inbound-agent, which creates/updates the ElevenLabs agent and re-binds the inbound numbers to this agent_key. Without this sync, incoming calls on imported numbers are not routed to any AI agent. The sync sends: company id, agent_key, timezone, company_phone (handoff destination), voice, first_message, max_duration (900s), model_name, system_prompt, agent_availability (available if a company phone exists, else no_human_agents), and agent_language.
  4. System prompt assembly (useInboundAgentSync.buildSystemPrompt): in advanced mode the saved agent_instructions are used with [agent_name] substituted; otherwise a default inbound prompt is built from company_name, tone, and response_size. The opening uses opening_script (advanced) or a default greeting.
  5. Runtime. A call lands on a bound Twilio/Telnyx number → wallet gate (app-bought numbers) → Call Forwarding chain (if configured) → working-hours check → ElevenLabs runs the agent → voice tools (book meeting, product lookup, escalateToHuman, follow-up, knowledge-base search, Simpro job for trade companies) execute via the tool-call protocol → ms-ai/ms-communication. On finish, the transcript is saved, sentiment/outcome classified, GHL tags applied, and the optional post-call webhook fires. See Call Agent and Telephony.

Configuration & options

AI Engine tab (AIEngine.vue)

FieldStored asNotes
Agent Voiceagent_voice_idChosen from the ElevenLabs voice catalog (GET {aiUrl}/elevenlabs/voices), with in-row audio preview and gender/accent/language filters.
Agent Languageagent_languageDefault en. From AI_CALL_SUPPORTED_LANGUAGES.
Modelmodel / model_providerModel picker is now visible. Options are the agent_type: 'call' rows from ms-ai GET /ai-model-pricing/estimates/:company_id (curated list in ai-model-pricing.controller.ts: claude-sonnet-4-5 default-first, plus GPT-4.1/5-mini/5.1/5.2 and Gemini rows). Models outside ElevenLabs' Llm enum silently fall back server-side to ELEVENLABS_INNER_LLM (default claude-sonnet-4-5).

Behavior tab (Behavior.vue)

FieldStored asNotes
Agent Nameagent_nameDefault {branding} Assistant. Substituted into the prompt's You are [agent_name], prefix.
Greeting Messageopening_scriptFirst line spoken when answering. Default: [enthusiastic warmly] Hi, thanks for calling {company_name}. How can I help you today?
Agent Instructionsagent_instructionsRequired. Stored with a You are [agent_name], prefix re-attached on save; the UI strips it for editing. Inbound agents are always saved as setting: 'advanced'.

Phone Numbers tab (PhoneNumber.vue)

  • Human-handoff number (transfer_number): where this agent forwards callers who ask for a human. Blank → falls back to the company number. Saving re-deploys to ElevenLabs so the escalation tool picks up the new value. At runtime the bridge reads it live from Mongo, presents the original caller's number as caller-ID (retrying with the owned number on carrier reject), and resolves Twilio credentials per dialed number — BYO and platform-bought numbers mix freely. See Telephony › Human escalation.
  • Buy a number in-app (GET {aiUrl}/elevenlabs/available-phone-numbers/twilioPOST {aiUrl}/elevenlabs/buy-phone-number/twilio/inbound): country picker US/CA/GB/AU, type local/toll-free/mobile, area-code/contains filters. The buy is gated on the telephony wallet covering the marked-up price (markup_percent); the first month is charged at purchase and the number lands in the company's Twilio subaccount. The tab shows the wallet balance and a "Top up wallet" link. See Telephony › Number lifecycle.
  • Import a Twilio number (POST {aiUrl}/elevenlabs/import-phone-number/twilio/inbound): requires the E.164 number, Twilio SID, and Twilio Auth Token. The import stamps this agent's handoff number (or company number) on the number's meta for runtime transfer resolution. Numbers are scoped to the agent's agent_key and filtered to purpose: 'inbound'. Delete is confirmed and irreversible.
  • Import a Telnyx number (POST {aiUrl}/elevenlabs/import-phone-number/telnyx/inbound): BYO telnyx_api_key + telnyx_connection_id (TeXML app); ms-ai points the TeXML app's voice URL at the ms-communication bridge.

Call Forwarding tab (CallForwarding.vuerouting_priorities)

Ordered list of real phone numbers to ring before the Voice AI — each row { number, ring_timeout } (timeout 5–120s, default 20s; max 10 rows; reorder/remove inline). Empty list = every call straight to the AI. Sanitized server-side in AiInboundAgentController._sanitizeRoutingPriorities; ms-communication dials the chain in order and falls through to the AI on exhaustion or any error. Applies regardless of working hours; the telephony wallet gate runs before the chain. Saving does not redeploy to ElevenLabs.

Post-call Tags tab (PostCallTags.vuepost_call_tags)

Tags the caller's GHL/LeadConnector contact with the call outcome. Built-in scenarios (fixed description, editable tag): booked, qualified — inbound callers are live humans, so no voicemail/no-answer built-ins (that's the outbound set). Plus custom rules (description + tag, AI-matched against the transcript). Applied by the backend via /internal/ai-call/contact-extracted; mirrors AiOutboundAgentController._sanitizePostCallTags.

Working Hours tab (WorkingHours.vueworking_hours)

When enabled, calls during these hours are transferred straight to a human (the transfer number below); outside these hours — or when disabled — the AI answers. (Note the polarity: in-hours = human, off-hours = AI.)

FieldStored asNotes
Enable working hoursworking_hours.enabledOff = AI handles every call.
Transfer numberworking_hours.transfer_numberIn-hours forwarding destination; blank → company number.
Timezoneworking_hours.timezoneHours evaluated in this timezone (default company timezone / UTC).
Per-day scheduleworking_hours.days.{mon..sun}Each { enabled, start, end }. Defaults: weekdays 09:00–17:00 enabled, weekends closed. "Apply to all days" copies a row's times.

Webhook tab (Webhook.vue)

FieldStored asNotes
Webhook URLpost_call_webhook_urlOptional; must start with http:///https:// (≤2048 chars). On call end we POST the full call under a next_level_call namespace: transcript + transcript_text, summary, call_sentiment, signed recording_url, phone numbers, direction, duration, agent_key. Held until the ElevenLabs analysis lands; backend PostCallWebhookFallbackJob fires it after 5 min if the analysis never arrives (exactly-once). Blank disables.
Custom headerpost_call_webhook_headerOptional single line Name: value (≤1024 chars), e.g. Authorization: Bearer ….

For QA/Support: every tab tracks unsaved edits and prompts before navigation. Saves surface toasts; ElevenLabs deploy failures show "Failed to deploy … to ElevenLabs" without blocking the DB save.

Behaviors & edge cases

  • Deploy-on-save is essential. Behavior, AI Engine, and handoff-number saves re-deploy to ElevenLabs. The sync deliberately does not gate on model (the inner LLM is server-pinned), so agents created before the model default still deploy.
  • default agent is special. It maps to the legacy company.ai_inbound_call object and cannot be deleted (no delete button); it can be cloned.
  • Working-hours polarity. In-hours → human transfer, off-hours → AI. This is the opposite of the usual "open = staffed" intuition; it's designed for "let humans take calls during the day, AI covers after hours." Call Forwarding's routing_priorities ring before this check and apply regardless of working hours.
  • Wallet gate on app-bought numbers. When the telephony wallet is empty, inbound calls on platform-purchased numbers are answered with "This number is temporarily unavailable. Goodbye." (fail-open on errors; BYO numbers never gated).
  • Handoff fallbacks. Both the per-agent handoff number and the working-hours transfer number fall back to company.phone_number when blank.
  • agent_availability. Sent as available only if the company has a phone number; otherwise no_human_agents (affects whether the agent offers a human transfer).
  • Per-agent number scoping. Imported numbers are bound to a specific agent_key and filtered by purpose: 'inbound'; the Phone Numbers query is scoped server-side.

Plan & limits

  • Gating flag: the Inbound card and config are gated on company.ai_inbound_call_allowed (admin-controlled per company; default off). Agents.vue uses hasInboundCallAgent.
  • Agent cap: company.ai_inbound_call_agents_allowed (defaults to 1 — the default agent — when unset; admins raise it from the company detail page). The "Create agent" tile hides when the cap is reached or the package allows zero.
  • AI credits: inbound voice calls consume credits per minute (faster than chat) — both ElevenLabs voice minutes and the LLM brain count. Logged in AICreditLogModel (source voice_call). See AI Credits.
  • Telephony costs: app-purchased numbers bill the telephony wallet (= remaining_purchased_ai_credits, 1 credit = $0.01): monthly rental (daily 06:00 UTC cron), per-call Twilio cost (10-min sweep, subaccount-metered), all with the global telephony_markup_percent applied. Plan AI credits never pay telephony. Wallet < 0 → suspend → warnings day 3/6 → release after ~7d. BYO numbers bill the client's own carrier account. See Telephony subsystem.

Technical implementation

  • Frontend: frontend/src/views/settings/ai/agents/InboundCallAgent/index.vue (tabs), AgentList.vue (list/create/clone/delete + cap), AIEngine.vue (voice/language/model), Behavior.vue (name/greeting/instructions), PhoneNumber.vue (handoff + buy + Twilio import), CallForwarding.vue (routing priorities), WorkingHours.vue (per-day schedule), PostCallTags.vue (GHL tags), Webhook.vue (post-call), useInboundAgentSync.js (ElevenLabs deploy). Card: InboundCallAgentCard.vue. Composables: useInboundAgent.js, useTelephonyMarkup.js.
  • Backend: backend/app/routes/aiInboundAgentRoutes.jsAiInboundAgentController (list, create, show, update, destroy, rotateSecret, clone; sanitizers _sanitizeRoutingPriorities, _sanitizePostCallTags). Config persists in company.ai_inbound_call (default agent) and company.ai_inbound_call_agents[].
  • ms-ai (ElevenLabs): ms-ai/src/elevenlabs/elevenlabs.controller.ts (POST /sync-inbound-agent, POST /import-phone-number/twilio/inbound, GET /voices), elevenlabs.service.ts (prompt/tool assembly, voicemail/transfer handling, Simpro job-booking section). Entities: elabs-agents.entity.ts, elabs-phone-numbers.entity.ts (both carry the agent_key discriminator). Voice tools in ms-ai/src/knox-voice/. See ms-ai.
  • Runtime bridge & monitoring: calls route through ms-communication so the dashboard can monitor/take over. Nox reports call config/outcomes but doesn't place calls — see Nox assistant.