Skip to main content

Outbound Call Agent

Audience: Sales · Management · QA · Support · Engineering · Where in app: Settings → AI Agents → Outbound Call Agent (per-agent tabs: AI Engine, Behavior, Phone Numbers, Webhook, GHL Webhooks and Tags) · Plan availability: Per-company admin gate ai_outbound_call_allowed — see Plan & limits

The Outbound Call Agent places calls to your leads and customers — qualifying them, booking meetings, chasing invoices, and following up — with an AI voice that sounds like a professional human rep. You build the agent's prompt with a guided Template Wizard, give it a voice and a Twilio number, then fire calls via a signed webhook from any automation (GoHighLevel, Zapier, n8n, or your own backend). Each company can run multiple outbound agents, each a slot in company.ai_outbound_call_agents keyed by agent_key, with its own prompt, voice, phone number(s), webhook_secret, and post-call tagging rules.

What it does

  • Places outbound AI voice calls to a destination number, injecting the lead's name/email and optional per-call context into the agent prompt.
  • Builds the agent prompt via a 7-step Template Wizard (identity, objective, lead context, opening, discovery, objection handling, boundaries & close) — or you edit the instructions directly.
  • Is triggered by a signed webhook (POST /ai-outbound/webhook) so CRM/automation tools can dial on demand; auth is the agent's webhook_secret.
  • Can be triggered from the browser by the Headless SDK via POST /ai-outbound/sdk-trigger — the browser sends only { company_id, to_phone, name?, email? }; the backend resolves the agent + secret server-side (never exposed), rate-limits (per-number 5-min cooldown, 20/company/hour, plus IP limiter), and reuses the signed webhook pipeline.
  • Accepts custom_context per call — highest-priority guidance injected right after the opening (e.g. "Focus on the enterprise plan and book a demo", or a GHL merge field).
  • Tags the GHL contact after the call with the outcome (no-answer, voicemail, booked, qualified) plus your own AI-matched custom rules, so downstream automations can react.
  • Can chase invoices / book jobs using integration context — e.g. Simpro job-booking voice tools for trade companies (cost-centre-aware classification). (verify the live invoice-chasing flow end-to-end.)

How it works

  1. Create an agent. From the agent list (AgentList.vue) you create an agent with a name (POST /ai-outbound-agents). Agents can be cloned and have their webhook_secret rotated.
  2. Configure across the tabs (saves go through PATCH /ai-outbound-agents/:agent_key):
    • AI Engine — voice + language (model server-pinned, mirrors inbound).
    • Behavior — business context + Template Wizard + opening script + instructions.
    • Phone Numbers — import the Twilio number this agent dials from.
    • Webhook — the generic signed trigger endpoint + secret.
    • GHL Webhooks and Tags — the GHL trigger payload + post-call tagging rules.
  3. Build the prompt (Behavior tab → TemplateWizard.vue + templateAssembler.js): a 7-block wizard assembles agent_instructions + opening_script from your answers, stashing wizard_answers so it pre-fills next time. You can also edit instructions manually.
  4. Trigger a call (AiOutboundCallController.webhook): the caller POSTs { company_id, agent_key, secret_key, action: 'trigger_outbound_call', to_phone, ... }. The backend validates secret_key against that agent's webhook_secret, then proxies to ms-ai POST /elevenlabs/start-outbound-call, which routes through the ms-communication bridge (so the dashboard can monitor + take over). ms-ai resolves the agent's first imported number when elabs_phone_id is omitted.
  5. Per-call enrichment. Optional full_name/email are injected into the prompt; user_details_url (if provided) is GET-fetched and each top-level JSON field appended as a labelled bullet to the agent's "USER KEY INFO"; custom_context is injected as the highest-priority section for that call (unresolved {{...}} merge tokens are treated as empty).
  6. Post-call. Transcript saved, sentiment classified (call_sentiment from the caller's words), outcome classified (call_outcomebooked|qualified|voicemail|no_answer|no_outcome, upgrade-only — a late ElevenLabs analysis can promote qualifiedvoicemail/booked), and the GHL contact tagged per your rules — see GHL Webhooks and Tags. Voicemail = ElevenLabs analysis flag OR a deterministic transcript heuristic → record field voicemail. The agent's post-call webhook (held for the analysis; backend PostCallWebhookFallbackJob as the 5-min safety net) ships transcript_text, summary, call_sentiment, and a signed recording_url.

Configuration & options

AI Engine tab

Voice + agent language from the ElevenLabs catalog (same picker as inbound). The model picker is now visible — options are the agent_type: 'call' rows from ms-ai (claude-sonnet-4-5 default + GPT/Gemini rows); models outside ElevenLabs' Llm enum silently fall back server-side to ELEVENLABS_INNER_LLM.

Behavior tab (Behavior.vue)

Every agent edits in Advanced mode (the Quick tone/response-size path is hidden).

FieldStored asNotes
Agent Nameagent_nameSubstituted into the You are [agent_name], prompt prefix. Default {branding} Assistant.
IndustryindustryPre-filled from onboarding (industry_details). Feeds the wizard.
What do you offer?offeringPre-filled from onboarding.
Describe your business (one sentence)business_descriptionFeeds the generated prompt.
Opening Scriptopening_scriptFirst line spoken. Has a default sales pitch.
Agent Instructionsagent_instructionsRequired (advanced). Stored with the You are [agent_name], prefix re-attached on save.
Wizard answerswizard_answersPersisted only when the wizard ran this session (avoids clobbering with undefined).

Template Wizard steps (TemplateWizard.vue): 1) Identity & persona (name, business, tone); 2) Call objective (primary + fallback, with a custom-goal option); 3) Lead context (cold/warm/hot — only name/email/phone are passed at call time, so temperature sets the opening tone; cold auto-forces "ask permission to continue"); 4) The opening (style + optional custom script + ask-permission toggle); 5) Discovery (3–5 questions, pre-populated from the objective, add your own); 6) Objection handling (up to ~5 objection/response pairs); 7) Boundaries & close (never-do rules — standard defaults always appended — end action, max call length). "Generate template" assembles the instructions and opening into the form.

Phone Numbers tab

Import the Twilio number this agent dials from (POST {aiUrl}/elevenlabs/import-phone-number/twilio), bound to the agent's agent_key and filtered to non-inbound (purpose !== 'inbound'). Numbers can also be bought in-app (POST {aiUrl}/elevenlabs/buy-phone-number/twilio/outbound — US/CA/GB/AU, local/toll-free/mobile, telephony-wallet gated) or imported from Telnyx (POST {aiUrl}/elevenlabs/import-phone-number/telnyx, BYO telnyx_api_key + telnyx_connection_id); the number's provider selects the carrier at dial time. The index.vue Phone tab shows a "No number" badge when none is imported — outbound calls can't be placed until a number is imported. The same E.164 number can be imported to more than one agent. Wallet/rental/per-call billing: Telephony subsystem.

Webhook tab (Webhook.vue)

Shows the generic trigger endpoint (POST {baseUrl}/ai-outbound/webhook) and the agent's webhook_secret, for non-GHL automations (Zapier, n8n, custom backends). A public test endpoint (GET /ai-outbound/test-user-details) returns a sample lead payload for use as user_details_url when test-firing. Two secret-less entry points reuse the same pipeline: POST /ai-outbound/sdk-trigger (Headless SDK, secret resolved server-side, rate-guarded) and an internal Nox trigger (internalTrigger, keyed on the caller's chosen agent, races dial-start with a 10s optimistic timeout).

GHL Webhooks and Tags tab (GhlWebhooksAndTags.vue)

  • Trigger payload: a copy-paste JSON for a GHL Custom Webhook action pointing at POST {baseUrl}/ai-outbound/webhook, pre-filled with company_id, agent_key, secret_key (webhook_secret), action: 'trigger_outbound_call', and GHL merge fields ({{contact.id}}, {{contact.name}}, {{contact.email}}, {{contact.phone_raw}}). Includes an optional custom_context line (highest-priority per-call guidance; remove it if unused).
  • Post-call tagging (post_call_tags): four built-in outcomes with editable tag names — Call not answered (no_answer), Reached voicemail (voicemail), Meeting booked (booked), Spoke with a person, no booking (qualified). Plus custom rules: a description + tag where the AI reads the transcript and applies the tag when the description matches (e.g. "asked about pricing" → pricing). Half-filled custom rows are rejected on save; fully-empty rows are dropped.

For QA/Support: Behavior/AI Engine/Phone tabs track unsaved edits and prompt before navigation; "Save and Continue" auto-advances the wizard. Routing tab exists in code (Routing.vue, contact-source filter) but is currently disabled.

Behaviors & edge cases

  • Per-agent secret auth. The webhook validates secret_key against the specific agent's webhook_secret (403 on mismatch). Missing required fields → 400; unknown company → 404; unknown agent_key → 404; unknown action → 400.
  • Unresolved merge tokens are ignored. If GHL sends {{contact.custom_context}} unresolved (no such custom field), the backend strips it so no meaningless instruction is injected.
  • No imported number = no call. ms-ai returns a clear 400 if the agent has no bound number; the UI also flags it with a "No number" badge.
  • custom_context is per-call priority. It overrides the agent's default guidance for that one call only, injected right after the opening.
  • user_details_url is best-effort. Fetch failures don't block the call; successful top-level JSON fields enrich the prompt.
  • Quick vs Advanced. The UI only exposes Advanced; the backend still supports a quick prompt path for legacy agents.
  • DNC suppression is built in. The shared /ai-outbound/webhook pipeline checks NoxGhlActionService.isSuppressed({ company_id, phone }) and refuses suppressed numbers (reason: 'suppressed:…') — every trigger path (GHL, SDK, Nox) inherits it.
  • Call windows / queue / concurrency: still no built-in scheduler — calls fire when the trigger arrives, so pacing and business-hour windows remain the upstream automation's job. The only rate guards are on sdk-trigger (per-number 5-min cooldown, 20/company/hour, 429 over-limit).

Plan & limits

  • Gating flag: the Outbound card and config are gated on company.ai_outbound_call_allowed (admin-controlled per company; default off). Agents.vue uses hasOutboundCallAgent. The KB also references company.ai_outbound_call_enabled as the operational switch alongside the admin gate.
  • AI credits: outbound voice calls consume credits per minute (ElevenLabs voice minutes + the LLM brain), logged in AICreditLogModel (source outbound_voice_call). See AI Credits.
  • Telephony costs: app-purchased numbers bill the telephony wallet (monthly rental cron + per-call Twilio cost sweep of the company subaccount, telephony_markup_percent applied); BYO numbers bill the client's own carrier. Plan AI credits never pay telephony. See Telephony subsystem.
  • Sales/Management framing: Outbound Call Agent turns CRM events into AI phone outreach (lead qualification, invoice chasing, callbacks) with measurable outcomes via GHL tagging.

Technical implementation

  • Frontend: frontend/src/views/settings/ai/agents/OutboundCallAgent/index.vue (tabs + "No number" badge), AgentList.vue (list/create/clone/delete), AIEngine.vue (voice/language), Behavior.vue (business context + wizard), TemplateWizard.vue + templateAssembler.js (7-block prompt builder), PhoneNumber.vue (Twilio import, outbound), Webhook.vue (generic trigger + secret), GhlWebhooksAndTags.vue (GHL payload + post-call tags), Routing.vue (disabled). Card: OutboundCallAgentCard.vue. Composable: frontend/src/composables/useOutboundAgent.js. Repository: frontend/src/repositories/admin/ai-outbound-agents.js.
  • Backend: backend/app/routes/aiOutboundAgentRoutes.jsAiOutboundAgentController (CRUD + rotate-secret + clone); backend/app/routes/aiOutboundCallRoutes.jsAiOutboundCallController (POST /ai-outbound/webhook signed trigger, POST /ai-outbound/sdk-trigger, GET /ai-outbound/test-user-details, internal Nox trigger). Config persists in company.ai_outbound_call_agents[] (each: agent_key, agent_name, agent_instructions, opening_script, voice, model, webhook_secret, post_call_tags, wizard_answers, setting). GHL sync via OutboundCallGhlSyncModel.
  • ms-ai (ElevenLabs): ms-ai/src/elevenlabs/POST /elevenlabs/start-outbound-call (routes to startTelnyxOutboundCall when the number's provider is telnyx), POST /import-phone-number/twilio, POST /import-phone-number/telnyx, POST /buy-phone-number/twilio/outbound, prompt/tool assembly incl. the Simpro job-booking section (SimproToolService, cost-centre map). Entities carry the agent_key discriminator for multi-agent outbound. Voice tools in ms-ai/src/knox-voice/. See ms-ai.
  • Runtime bridge: calls route through ms-communication for monitor/take-over (carrier-agnostic, keyed by service_room_id), sentiment/voicemail/outcome classification, and the post-call webhook. Nox can place calls via knox-place-call when enabled — see Nox assistant.