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'swebhook_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_contextper 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
- 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 theirwebhook_secretrotated. - 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.
- Build the prompt (Behavior tab →
TemplateWizard.vue+templateAssembler.js): a 7-block wizard assemblesagent_instructions+opening_scriptfrom your answers, stashingwizard_answersso it pre-fills next time. You can also edit instructions manually. - Trigger a call (
AiOutboundCallController.webhook): the caller POSTs{ company_id, agent_key, secret_key, action: 'trigger_outbound_call', to_phone, ... }. The backend validatessecret_keyagainst that agent'swebhook_secret, then proxies to ms-aiPOST /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 whenelabs_phone_idis omitted. - Per-call enrichment. Optional
full_name/emailare 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_contextis injected as the highest-priority section for that call (unresolved{{...}}merge tokens are treated as empty). - Post-call. Transcript saved, sentiment classified (
call_sentimentfrom the caller's words), outcome classified (call_outcome∈booked|qualified|voicemail|no_answer|no_outcome, upgrade-only — a late ElevenLabs analysis can promotequalified→voicemail/booked), and the GHL contact tagged per your rules — see GHL Webhooks and Tags. Voicemail = ElevenLabs analysis flag OR a deterministic transcript heuristic → record fieldvoicemail. The agent's post-call webhook (held for the analysis; backendPostCallWebhookFallbackJobas the 5-min safety net) shipstranscript_text,summary,call_sentiment, and a signedrecording_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).
| Field | Stored as | Notes |
|---|---|---|
| Agent Name | agent_name | Substituted into the You are [agent_name], prompt prefix. Default {branding} Assistant. |
| Industry | industry | Pre-filled from onboarding (industry_details). Feeds the wizard. |
| What do you offer? | offering | Pre-filled from onboarding. |
| Describe your business (one sentence) | business_description | Feeds the generated prompt. |
| Opening Script | opening_script | First line spoken. Has a default sales pitch. |
| Agent Instructions | agent_instructions | Required (advanced). Stored with the You are [agent_name], prefix re-attached on save. |
| Wizard answers | wizard_answers | Persisted 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 withcompany_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 optionalcustom_contextline (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_keyagainst the specific agent'swebhook_secret(403 on mismatch). Missing required fields → 400; unknown company → 404; unknownagent_key→ 404; unknownaction→ 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_contextis per-call priority. It overrides the agent's default guidance for that one call only, injected right after the opening.user_details_urlis 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
quickprompt path for legacy agents. - DNC suppression is built in. The shared
/ai-outbound/webhookpipeline checksNoxGhlActionService.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.vueuseshasOutboundCallAgent. The KB also referencescompany.ai_outbound_call_enabledas 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(sourceoutbound_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_percentapplied); 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.js→AiOutboundAgentController(CRUD +rotate-secret+clone);backend/app/routes/aiOutboundCallRoutes.js→AiOutboundCallController(POST /ai-outbound/webhooksigned trigger,POST /ai-outbound/sdk-trigger,GET /ai-outbound/test-user-details, internal Nox trigger). Config persists incompany.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 viaOutboundCallGhlSyncModel. - ms-ai (ElevenLabs):
ms-ai/src/elevenlabs/—POST /elevenlabs/start-outbound-call(routes tostartTelnyxOutboundCallwhen the number'sprovideristelnyx),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 theagent_keydiscriminator for multi-agent outbound. Voice tools inms-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 viaknox-place-callwhen enabled — see Nox assistant.
Related
- Call Agent (shared voice tools, post-call enrichment, quality scoring)
- Inbound Call Agent
- Telephony subsystem (carriers, number lifecycle, wallet, escalation)
- AI Credits
- ms-ai service
- ms-communication service