External AI Provider Hooks
Ctrl+K
Docs Home
Docs Home / API Reference / External AI Provider Hooks

External AI Provider Hooks

These endpoints let an externally managed AI provider — a hosted AI voice/chat platform that runs the conversation on its own infrastructure — interface with Cloudspire. Through them the provider receives per-call context, reports call lifecycle events (which drive call history, campaign results, and notifications platform-side), and invokes PBX tools mid-call (directory lookups, availability checks, message taking, CRM actions).

These hooks are currently dormant. No external AI provider is active on the platform — AI conversations run on Cloudspire's internal AI engine, which does not use these HTTP endpoints. Two things are deliberately separate: the External AI Agent inbound destination is pure SIP trunk forwarding — it delivers the call to the provider and creates no platform-side agent identity, so on its own it does not enable any hook on this page (see DIDs & Inbound Routing). The hooks below authenticate against agents and response-engine tokens configured inside Cloudspire; using them for a new provider integration requires that platform-side provisioning to exist, which is an engineering engagement rather than a self-service step today.

Base URL

https://api.cloudspirevoice.com/

Endpoint Overview

EndpointDirectionPurposeAuthentication
/external-ai/webhook.phpProvider → CloudspireCall/chat lifecycle eventsWebhook signature header (IP allowlist fallback)
/external-ai/dynamic-vars.phpProvider → CloudspireInbound-call dynamic variablesNone (echo-only)
/ai/pbx-tools.phpProvider → CloudspireMid-call PBX / CRM tool callsEngine API token (Bearer header preferred; ?token= legacy)
/external-ai/admin-tools.phpProvider → CloudspireInternal assistant tools (platform system agents only)Engine API token (query param or Bearer)
/agent/campaign-intake.phpYour system → CloudspireQueue outbound AI callsCampaign API key (Bearer)

Call Lifecycle Webhook

The single webhook endpoint for all provider events. The provider POSTs one event per state change of a voice call or chat session; Cloudspire validates the signature, resolves the agent, logs the event to the AI call history, and runs the platform-side effects for that event type — outbound-campaign result attribution, result-webhook fan-out, demo summary emails, and critical-error alerting.

Endpoint

POST https://api.cloudspirevoice.com/external-ai/webhook.php

Authentication

Requests are authenticated by a signature header:

x-retell-signature: v={timestamp_ms},d={hmac_sha256_hex}

The digest is HMAC-SHA256(webhook_signing_key, raw_request_body + timestamp_ms) over the raw POST body exactly as sent, with the timestamp appended as a decimal string of milliseconds. The signing key is the provider integration's webhook key, configured platform-side by your Cloudspire administrator (a platform-level setting, not per customer). A signature older than the configured maximum age (default 300 seconds) is rejected.

Event Types

Every accepted event is written to the AI call history (visible in the admin portal's AI request logs) with the call/chat ID, numbers, duration, cost, and disconnection reason. In addition, specific events trigger platform-side processing:

EventChannelPlatform-side effect
call_startedvoiceLogged to call history.
call_endedvoiceLogged with duration, disconnection reason, and cost. If the call was placed by the AI outbound campaign system (its metadata carries the queue reference), the queue record is updated: terminal status (completed / voicemail / failed) or an automatic retry is scheduled per the campaign's retry and voicemail policy, and the transcript is stored (recording URLs are not persisted -- see the field table below).
call_analyzedvoiceLogged with sentiment and summary. Result-webhook deliveries are queued to the customer's configured result webhook URLs (requires the API webhook capability in the customer's plan). For outbound-campaign calls, the AI summary, sentiment, and success flag are written to the queue record, and the campaign's completion callback fires if the call has already reached a terminal status -- when call_analyzed arrives before call_ended (an ordering this legacy endpoint permits), no callback is sent for that call. For demo agents, one consolidated demo summary email is sent (summary, sentiment, caller, duration, plus any structured tool submissions captured during the call).
transcript_updatedvoiceLogged to call history.
transfer_started / transfer_bridged / transfer_cancelled / transfer_endedvoiceLogged with the transfer destination.
chat_started / chat_ended / chat_analyzedchatLegacy, no longer resolvable: provider chat-agent identifiers cannot be mapped to a platform agent any more (chat runs on the internal engine), so these events are acknowledged with 200 and discarded. This webhook is effectively voice-scoped.

Independently of the event type, an event whose disconnection_reason is one of the critical values concurrency_limit_reached, no_valid_payment, scam_detected, or error_retell triggers an immediate alert email to the platform's configured alert address.

Request Body

Voice events carry the session under a call object; chat events carry a chat object with the analogous field names (chat_id, chat_status, chat_type, chat_analysis, chat_cost).

{
  "event": "call_ended",
  "call": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz789",
    "call_type": "phone_call",
    "from_number": "+17705551234",
    "to_number": "+14045556789",
    "call_status": "ended",
    "start_timestamp": 1767000000000,
    "end_timestamp": 1767000092000,
    "disconnection_reason": "user_hangup",
    "transcript": "...",
    "recording_url": "https://...",
    "metadata": {},
    "call_analysis": {
      "call_summary": "...",
      "user_sentiment": "Positive",
      "call_successful": true,
      "in_voicemail": false
    },
    "call_cost": { "combined_cost": 42 }
  }
}

Request Fields

FieldTypeRequiredDescription
eventstringYesOne of the event types above.
call.call_idstringYesThe provider's unique session ID (chat.chat_id for chat). Used as the correlation ID across history, tool submissions, and campaign records.
call.agent_idstringYesThe provider-side agent ID configured on the Cloudspire AI agent. Events without it (or with an unknown value) are acknowledged and ignored.
call.call_typestringNoProvider call type (e.g. phone_call). Logged.
call.from_number / call.to_numberstringNoCaller / called number in E.164. Logged.
call.start_timestamp / call.end_timestampintegerNoUnix milliseconds. Their difference is the recorded duration.
call.disconnection_reasonstringNoWhy the session ended (e.g. user_hangup, agent_hangup, no_answer, busy, dial_failed, voicemail_reached). Drives outbound-queue status mapping and critical alerting.
call.transfer_destinationstringNoDestination of a transfer event. Logged.
call.transcriptstringNoFull transcript; stored on outbound-campaign queue records at call_ended.
call.recording_urlstringNoAccepted but currently ignored — the platform does not persist provider recording URLs (recordings for platform-run calls are captured platform-side).
call.metadataobjectNoFree-form session metadata set at call creation. For outbound campaign calls it carries the queue reference that ties the event back to the queue record (validated against the stored session ID — spoofed metadata is ignored).
call.call_analysisobjectNoPost-call analysis: call_summary (string), user_sentiment (string), call_successful (boolean), in_voicemail (boolean). Chat uses chat_analysis with chat_summary.
call.call_costobjectNo{"combined_cost": <cents>}. Logged; a zero/absent cost on chat_ended / chat_analyzed marks the session as empty and removes it from the logs.

Responses

HTTPBodyMeaning
200{"status": "ok"} or emptyAcknowledged, not guaranteed processed. 200 is returned for accepted events, for events discarded on a failed signature or unknown agent (empty body, deliberately 200 to suppress provider retries), and when a platform-side side effect (history write, queue update, notification) fails after acceptance — such failures are logged and alarmed platform-side rather than surfaced to the caller, and the provider must not retry. There is no durable-processing contract on this legacy endpoint.
400{"error": "..."}Empty body or invalid JSON.
405Method other than POST.
500Platform configuration could not be loaded. Safe to retry.

Example

curl -X POST https://api.cloudspirevoice.com/external-ai/webhook.php \
  -H "Content-Type: application/json" \
  -H "x-retell-signature: v={timestamp_ms},d={hmac_sha256_hex}" \
  -d '{
    "event": "call_ended",
    "call": {
      "call_id": "call_abc123",
      "agent_id": "agent_xyz789",
      "from_number": "+17705551234",
      "to_number": "+14045556789",
      "start_timestamp": 1767000000000,
      "end_timestamp": 1767000092000,
      "disconnection_reason": "user_hangup",
      "call_cost": { "combined_cost": 42 }
    }
  }'
HTTP/1.1 200 OK
{ "status": "ok" }

Inbound Dynamic Variables Webhook

Called by the provider before it answers an inbound call. The provider POSTs the caller information it has; Cloudspire returns the dynamic-variable map the provider substitutes into the agent's prompt (double-brace syntax, e.g. {{from_number}}). The endpoint echoes the caller and called numbers back in the provider's nested response contract.

Endpoint

POST https://api.cloudspirevoice.com/external-ai/dynamic-vars.php

Authentication

None. The endpoint performs no lookups and returns only values echoed from the request itself, so it carries no credential requirement.

Request Body

{
  "event": "call_inbound",
  "call_inbound": {
    "agent_id": "agent_xyz789",
    "from_number": "+17705551234",
    "to_number": "+14045556789"
  }
}
FieldTypeRequiredDescription
eventstringNocall_inbound in the provider's contract; not evaluated.
call_inbound.agent_idstringNoThe provider agent taking the call; accepted, not evaluated.
call_inbound.from_numberstringNoCaller's number. Echoed into the variable map (empty string when absent).
call_inbound.to_numberstringNoCalled number. Echoed into the variable map (empty string when absent).

A legacy flat body (the same fields at the top level, without the call_inbound wrapper) is also accepted.

Response

The variable map contains exactly two keys: from_number and to_number.

{
  "call_inbound": {
    "dynamic_variables": {
      "from_number": "+17705551234",
      "to_number": "+14045556789"
    }
  }
}

Errors: 400 {"error": "Invalid JSON"} for an unparseable body; 405 for a method other than POST.

Example

curl -X POST https://api.cloudspirevoice.com/external-ai/dynamic-vars.php \
  -H "Content-Type: application/json" \
  -d '{
    "event": "call_inbound",
    "call_inbound": {
      "agent_id": "agent_xyz789",
      "from_number": "+17705551234",
      "to_number": "+14045556789"
    }
  }'

PBX Tools API

The provider-agnostic mid-call tool endpoint. During a live conversation, the provider's LLM invokes custom tools by POSTing here; each call returns a short text result the AI reads (or paraphrases) to the caller. The tools query and act on the agent's own tenant — company directory, business hours, departments, presence, caller lookup, queue status, message taking, and (when licensed) the tenant's connected CRM.

Endpoint

POST https://api.cloudspirevoice.com/ai/pbx-tools.php
Authorization: Bearer {engine_api_token}

The legacy query form (?token={engine_api_token}) remains accepted for existing configurations but is deprecated — prefer the header.

Token handling. Use the Authorization: Bearer header. The legacy query form appears in URL-recording intermediaries' logs; if a URL containing a token may have been exposed, rotate the engine token. Cloudspire's own surfaces do not persist it: the request logger redacts credential parameters and the API server logs these routes without query strings.

Authentication

Invocation Envelope

{
  "name": "check_person_availability",
  "args": { "extension": "201" },
  "call": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz789"
  }
}
FieldTypeRequiredDescription
namestringYesThe tool to invoke (catalog below). Unknown names return 400.
argsobjectNoTool arguments (per-tool tables below). Defaults to {}.
call.call_idstringNoThe provider session ID, used to correlate the tool call with the call's history and logs. A top-level call_id is also accepted.
call.agent_idstringRecommendedThe provider agent ID (see Authentication). A top-level agent_id is also accepted.

Response

Every response — success or error — is a single JSON object with one key:

{ "result": "Sarah Johnson is available. RECOMMENDATION: transfer the call." }

The result string is written to be spoken by the AI. Error conditions use the same shape with an HTTP error status: 401 {"result": "Authentication error."}, 400 for a missing/unknown tool or malformed body, 500 for a handler failure, 503 when the platform database is unavailable.

Tool Catalog

Seven PBX tools are available to every AI agent. Arguments marked required must be present for a useful result.

ToolPurposeArguments
get_company_directory Search the company directory for a person by name (fuzzy/phonetic matching is handled platform-side). search_name (string, required) — the name exactly as the caller said it.
get_business_status Business status: open/closed right now, hours, department extensions, and the emergency number. none
get_departments List available departments (queues and hunt groups) with descriptions. none
check_person_availability Presence check before a transfer: available, in a meeting, away, DND, or on another call, plus any custom away-message and an explicit RECOMMENDATION line (transfer / offer voicemail / ask the caller). extension (string, required) — the extension number to check.
lookup_caller Identify the caller by phone number in the company phonebook. phone_number (string, required) — E.164.
get_queue_status How many agents are available in a call center queue before routing a caller there. extension (string, required) — the queue extension number.
submit_message Submit a message or request: a message for a person, a callback request, an after-hours support/sales inquiry, or an emergency escalation. Generates the tenant's configured notification (email routing honors department-specific addresses). request_type (string, required) — message | callback | after_hours_support | after_hours_sales | emergency
caller_name (string, required)
caller_phone (string, required)
issue_summary (string, required)
caller_company, caller_email (string, optional)
for_person (string, optional) — who the message is for (with request_type message)
reason (string, optional) — why the message is being taken
urgency (string, optional) — normal | urgent | emergency
department (string, optional) — routes the notification to that department's configured email

Deep-CRM Tools

Three additional tools operate on the tenant's connected CRM (HubSpot, Salesforce, or Zoho). They are entitlement-gated: the tool must be enabled on the agent's engine configuration and the customer's plan must include the advanced CRM capability. A gated tool that is not enabled/entitled returns 200 with {"result": "CRM tools are not enabled for this agent."}.

ToolPurposeArguments
crm_lookup_customer Look the caller up in the CRM; returns a record summary plus a crm_contact_id for follow-up actions. search (string, required) — E.164 phone number, email address, or full name.
crm_add_note Save a note to the customer's CRM record. note (string, required)
crm_contact_id (string, preferred) — from a prior lookup
search (string, fallback) — phone/email/name to locate the contact
crm_create_contact Create a new CRM contact for a caller with no record yet (new lead intake). last_name, phone (string, required)
first_name, email, company (string, optional)

Example

curl -X POST "https://api.cloudspirevoice.com/ai/pbx-tools.php" \
  -H "Authorization: Bearer {engine_api_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "check_person_availability",
    "args": { "extension": "201" },
    "call": { "call_id": "call_abc123", "agent_id": "agent_xyz789" }
  }'
HTTP/1.1 200 OK
{ "result": "Sarah Johnson is in a meeting. RECOMMENDATION: offer to take a message." }

Internal Assistant Tools

The HTTP tool door for the two internal back-office tools used by Cloudspire's own website and admin-portal assistants: filing a GitHub issue (bug report or feature request) and creating a CRM support ticket. It exists so an external provider running one of those platform system assistants can invoke the tools over HTTP; it is not available to customer agents — only agents flagged platform-side as system agents pass authentication.

Endpoint

POST https://api.cloudspirevoice.com/external-ai/admin-tools.php

Authentication

Request Body

Tool arguments arrive wrapped in an args object (the provider's custom-tool convention); a flat body with the same fields at the top level is also accepted. The action field selects the tool.

{
  "agent_id": "agent_xyz789",
  "args": {
    "action": "create_support_ticket",
    "subject": "Portal login issue",
    "description": "Customer reports the portal rejects their password after reset.",
    "priority": "high",
    "contact_name": "John Doe",
    "contact_email": "[email protected]"
  }
}

Action: create_github_issue

FieldTypeRequiredDescription
typestringYesbug or feature. Anything else is rejected.
titlestringYesIssue title.
descriptionstringYesIssue body. A submitted-via-AI footer is appended.
categorystringNopbx | call-center | provisioning | messaging | reporting | docs | mobile; anything else defaults to pbx. Applied as an issue label.
prioritystringNolow | medium | high | critical; defaults to medium. Applied as an issue label.

Success response:

{
  "success": true,
  "issue_number": 1042,
  "issue_url": "https://github.com/.../issues/1042",
  "message": "Created bug report #1042. The team will review it shortly."
}

The support team is additionally notified by email. Failures return {"success": false, "error": "..."}.

Action: create_support_ticket

FieldTypeRequiredDescription
subjectstringYesTicket subject.
descriptionstringYesTicket description (becomes the ticket's visible opening reply).
categorystringNoA CRM ticket category name; unknown names fall back to Support.
prioritystringNolow | medium | high | urgent; defaults to medium.
contact_namestringNoThe requester's name, recorded on the ticket.
contact_emailstringNoMatched against customer records: a match attributes the ticket to that customer account; an unmatched address is kept as a CC so replies still reach the requester.

Success response:

{
  "success": true,
  "ticket_number": "TKT-10231",
  "ticket_id": 512,
  "message": "Support ticket TKT-10231 has been created. The support team will review it shortly."
}

An unknown action returns 400 {"error": "Unknown action. Use create_github_issue or create_support_ticket"}. Other errors: 401 missing/invalid credential, 405 non-POST, 400 invalid JSON, 500 database unavailable.

Example

curl -X POST "https://api.cloudspirevoice.com/external-ai/admin-tools.php" \
  -H "Authorization: Bearer {engine_api_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_xyz789",
    "args": {
      "action": "create_github_issue",
      "type": "bug",
      "title": "Queue wallboard shows stale agent counts",
      "description": "Reported via the admin assistant: the wallboard lags by several minutes.",
      "category": "call-center",
      "priority": "high"
    }
  }'

AI Outbound Calling

The outbound half of the surface — POST /agent/campaign-intake.php, which queues outbound AI calls from your own system — has its own full reference, including authentication (per-campaign Bearer key), the request/response schemas, queue statuses, and the completion callback: see the AI Outbound Calling API.

Not Part of This Surface

One further endpoint exists alongside these hooks but is not a customer-facing integration point: POST /ai/demo-intake.php?token={engine_api_token} accepts mid-call tool submissions (reservations, appointment requests, intake forms, and similar) only from agents flagged as demo agents — the assistants used for Cloudspire's own marketing demos. Any non-demo agent is rejected with 403, so it is listed here for completeness only.