Skip to content

IVR Call Transfer Backend Reference ​

Overview ​

The IVR Call Transfer feature allows AI agents to transfer callers to external phone numbers. Two modes are supported:

  • Cold Transfer SIP REFER redirect. Caller is forwarded directly, LiveKit session ends.
  • Warm Transfer Agent dials the target, navigates IVR menus via DTMF tones, then bridges the original caller into the conversation.

Customers configure IVR actions (transfer targets, DTMF sequences, descriptions) through the API. Each action becomes a separate LLM tool at runtime.

Feature Gate ​

Feature string: "ivr_call_transfer" in AVAILABLE_FEATURES

Feature map: "manageIvrActions" → ["ivr_call_transfer"] in FEATURE_MAP

The requireFeatureAccess middleware enforces this gate on write endpoints. The feature must be present in both the Organization's plan.features and the Customer's features arrays.

API Endpoints ​

GET /api/agents/:agentId/ivr-actions ​

Returns the agent's configured IVR actions.

MiddlewareverifyJwt
AuthJWT user must own the agent (agent.userId === req.userId)
Response{ success: true, ivrActions: IIvrAction[] }

PUT /api/agents/:agentId/ivr-actions ​

Replaces the agent's entire ivrActions array. Sets is_agent_updated = true.

MiddlewareverifyJwt, requireRole("customer"), requireFeatureAccess("manageIvrActions")
AuthJWT user must own the agent
Request body{ ivrActions: IIvrAction[] }
Response{ success: true, ivrActions: IIvrAction[] }

GET /internal/agent-ivr-actions/:agentId ​

Internal endpoint for Agent Server inbound call fallback. Returns IVR actions without JWT auth.

MiddlewareverifyInternalWebhook (x-api-secret header)
Response{ success: true, ivrActions: IIvrAction[] }

Data Model ​

IVR actions are stored as a sub-array on the Agent document (same pattern as tools, backchannels, templateVariables).

typescript
// Agent schema ivrActions field
ivrActions: [{
  actionId:       { type: String, required: true },       // Unique within the array
  name:           { type: String, required: true },        // Max 64 chars
  description:    { type: String, required: true },        // Max 512 chars, used as LLM tool description
  transferTarget: { type: String, required: true },        // E.164 or SIP URI
  transferType:   { type: String, enum: ["cold", "warm"], default: "cold" },
  dtmfSequence:   [{
    type:  { type: String, enum: ["digit", "wait"], required: true },
    value: { type: String, required: true },               // DTMF char or milliseconds
  }],
}]

TypeScript interface:

typescript
interface IIvrAction {
  actionId: string;
  name: string;
  description: string;
  transferTarget: string;
  dtmfSequence?: Array<{ type: "digit" | "wait"; value: string }>;
  transferType: "cold" | "warm";
}

Validation Rules ​

FieldRule
actionIdRequired string, unique within the array
nameRequired, non-empty, max 64 characters
descriptionRequired, non-empty, max 512 characters
transferTargetRequired, must match E.164 (/^\+[1-9]\d{1,14}$/) or SIP URI (/^sip:.+/)
transferTypeRequired, "cold" or "warm"
dtmfSequence[].type"digit" or "wait"
dtmfSequence[].value (digit)One of 0-9, *, #
dtmfSequence[].value (wait)Integer between 100 and 30000 ms
Actions per agentMaximum 20
Duplicate actionIdRejected with validation error

Validation is handled by validateIvrAction() in ivr-action.controller.ts. All fields are trimmed before persistence.

Dispatch Metadata Integration ​

IVR actions are embedded in the dispatch metadata payload sent to the Agent Server when creating a LiveKit room. This avoids extra API calls during live outbound/test calls.

typescript
// DispatchMetadata ivr_actions field
ivr_actions?: Array<{
  actionId: string;
  name: string;
  description: string;
  transferTarget: string;
  dtmfSequence?: Array<{ type: "digit" | "wait"; value: string }>;
  transferType: "cold" | "warm";
}>;

The dispatch builder reads agent.ivrActions and includes it as ivr_actions in the metadata. For inbound calls where dispatch metadata may not contain IVR actions, the Agent Server falls back to the internal API endpoint.

Agent Server Integration ​

On the Agent Server (Python/LiveKit), IVR actions flow through:

  1. MetadataSchema Pydantic model parses ivr_actions from dispatch metadata into IvrActionDef objects.
  2. Tool Registration create_ivr_tool_functions() creates one LiveKit FunctionTool per action, using the action's name and description.
  3. Cold Transfer Tool calls TransferSIPParticipant (SIP REFER).
  4. Warm Transfer Tool dials the target via create_sip_participant, executes the DTMF sequence step by step, then bridges the caller via MoveParticipant.
  5. IVR Detection When ivr_actions are present, session_builder passes ivr_detection=True to AgentSession.
  6. Inbound Fallback If ivr_actions is absent from metadata, the Agent Server fetches from GET /internal/agent-ivr-actions/:agentId.

Error Responses ​

ScenarioStatusResponse
Customer lacks ivr_call_transfer feature403{ success: false, message: "You don't have access to this feature..." }
Invalid field values (name, description, target)400{ success: false, message: "<validation error>", errors: [...] }
Invalid DTMF digit400{ success: false, message: "Invalid DTMF digit: must be 0-9, *, or #" }
Wait value out of range400{ success: false, message: "Wait value must be between 100 and 30000 ms" }
More than 20 IVR actions400{ success: false, message: "Maximum 20 IVR actions per agent" }
Duplicate actionId400{ success: false, message: "Duplicate actionId: \"...\"" }
ivrActions not an array400{ success: false, message: "ivrActions must be an array" }
Agent not found404{ success: false, message: "Agent not found" }
Agent belongs to different customer403{ success: false, message: "Not authorized to modify this agent" }
Server error500{ success: false, message: "Failed to update IVR actions" }

File Map ​

controllers/ivr-action.controller.ts  Validation + CRUD handlers (getIvrActions, updateIvrActions, getIvrActionsInternal)
routes/ivr-action.routes.ts           Route definitions + middleware chains
models/Agent.ts                       ivrActions field on Agent schema
types/index.ts                        IIvrAction interface, AVAILABLE_FEATURES, FEATURE_MAP entries

Relationship to Call Flow Builder & AI Actions Panel ​

IVR actions are part of the unified Call Flow Builder system. The same data is presented in two UI views:

UI ViewLocationPresentation
AI Actions PanelAgent Editor → Tools & Transfer → AI Actions tabToggle-based list with config forms
Call Flow BuilderStandalone page or Agent Editor → Call Flow Builder stepVisual canvas with draggable AI Transfer nodes

Changes in either view are synced to the other via the shared callFlowStore (Zustand). When a user enables an AI action (e.g., "Transfer to Number") in the toggle panel, it automatically:

  1. Appears as a standalone AI Transfer node on the Call Flow Builder canvas
  2. Auto-enables the "Enable Call Flow" toggle for the relevant direction (inbound/outbound)

For the full system architecture, see docs/CALL-FLOW-BUILDER.md.

Use Cases & Examples ​

1. Cold Transfer Escalate to a person ​

Scenario: Customer calls AI agent and says "I want to speak to a manager."

  • Agent says "Let me transfer you, please hold."
  • SIP REFER forwards the call directly to the manager's number.
  • Agent session ends. Caller is connected to the manager.

Configuration:

json
{
  "actionId": "uuid-1",
  "name": "transfer_to_manager",
  "description": "Transfer the caller to a manager when they request human assistance",
  "transferTarget": "+919876543210",
  "transferType": "cold",
  "dtmfSequence": []
}

2. Cold Transfer Forward to a department ​

Scenario: "Transfer me to billing" → agent forwards to the billing team's direct line.

Same as above with a different target number. No IVR navigation needed because the target is a direct line.


3. Warm Transfer with DTMF Navigate an IVR menu ​

Scenario: Customer wants to be transferred to their bank's fraud department. The bank's number has an IVR: "Press 1 for English → Press 3 for fraud → Press # to confirm."

  • Agent dials the bank's number, navigates the IVR via DTMF tones.
  • Once the fraud department answers, agent bridges the caller in.
  • Caller is now connected to the fraud department rep.

Configuration:

json
{
  "actionId": "uuid-2",
  "name": "transfer_to_bank_fraud",
  "description": "Transfer the caller to the bank fraud department",
  "transferTarget": "+911800123456",
  "transferType": "warm",
  "dtmfSequence": [
    { "type": "wait", "value": "3000" },
    { "type": "digit", "value": "1" },
    { "type": "wait", "value": "2000" },
    { "type": "digit", "value": "3" },
    { "type": "wait", "value": "1000" },
    { "type": "digit", "value": "#" }
  ]
}

4. Warm Transfer without DTMF Connect to a supervisor ​

Scenario: AI handles a complaint call, customer is angry and wants a human. Supervisor's number is a direct mobile no IVR.

  • Agent dials the supervisor into the caller's room.
  • Supervisor answers, is now in the same room as the caller.
  • Agent leaves. Caller and supervisor continue.

Configuration:

json
{
  "actionId": "uuid-3",
  "name": "transfer_to_supervisor",
  "description": "Connect the caller to a supervisor for escalation",
  "transferTarget": "+919988776655",
  "transferType": "warm",
  "dtmfSequence": []
}

5. Multiple transfer targets per agent ​

Scenario: A real estate AI agent has three transfer options:

ActionTargetTypeDTMF
Transfer to Sales+91-9876543210cold
Transfer to Site Visit Booking+91-1800-XXX-XXXXwarm2 → wait → 1
Transfer to Manager+91-9988776655warm

The LLM sees all three as separate tools and picks the right one based on conversation context. If the user says "I want to book a site visit", the LLM calls transfer_to_site_visit_booking.


6. Inbound call transfer ​

Scenario: Customer calls your inbound number, talks to the AI, then asks to speak to a human.

Works identically to outbound the agent finds the SIP participant in the room and initiates the transfer. Both cold and warm work for inbound calls.


Summary ​

Use CaseTransfer TypeDTMFWhen to use
Escalate to a personColdNoTarget is a direct line (mobile, desk phone)
Forward to departmentColdNoTarget is a department's direct number
Navigate target's IVR then connectWarmYesTarget has an IVR menu to navigate
Connect to supervisor directlyWarmNoTarget is a direct line but you want agent to stay until connected
Multiple options per agentBothOptionalAgent needs different transfer destinations
Inbound caller wants transferBothOptionalSame as outbound, works for inbound too

Self-Hosted LiveKit Note ​

Warm transfer works on self-hosted LiveKit. The MoveParticipant API is Cloud-only, but our implementation uses a single-room approach instead: the supervisor is dialed directly into the caller's room, and the agent leaves. The end result is identical.

Last updated: