Transfer Call Options
Route callers to human agents or other numbers — blind or attended, sequential or simultaneous, with dynamic resolution and failure handling.
Overview
VoiceElements supports transferring a caller to a human agent or another number, controlled by the AI. Transfers can be blind (caller is immediately bridged) or attended (the AI announces the caller to the recipient first). Numbers can be dialed sequentially (one at a time, move to next on no answer) or simultaneously (all at once, first to answer wins).
Transfers are initiated when the AI calls the vns_transfer_call built-in tool.
You configure transfer behavior through transferServices in
VoiceElements_Instructions in your
CallStart response.
Blind Transfer
Caller is bridged immediately. The AI disconnects once the recipient answers.
Attended Transfer
The AI announces the caller to the recipient and gets confirmation before bridging. If the transfer doesn't complete, the caller is restored to the AI.
Dynamic Resolution
Your API can return a transfer service at call time based on time of day, queue availability, or any business logic.
The vns_transfer_call Tool
You must define vns_transfer_call in your session configuration so the AI knows it can request a transfer.
The named-service form is recommended.
Recommended — AI selects a named transfer service
Use this when you have one or more transfer services (e.g. Support, Billing) and want the AI to choose by name. List exactly your configured service names in enum so the model can only pick a service that exists. The AI never sees the phone numbers.
{
"name": "vns_transfer_call",
"description": "Transfers the current call using a configured transfer service.",
"type": "function",
"parameters": {
"type": "object",
"properties": {
"transferService": {
"type": "string",
"description": "The configured transfer service to use.",
"enum": ["Support", "Billing"]
}
},
"required": ["transferService"],
"additionalProperties": false
}
}
Simple — uses the "default" transfer service
No parameters needed when you have a single default transfer destination:
{
"name": "vns_transfer_call",
"description": "Transfers the current call to another number.",
"type": "function",
"parameters": null
}
Transfer Service Resolution Order
When vns_transfer_call is received,
VoiceElements resolves the transfer service using this chain in order:
Named service
If transferService is provided and matches a name in transferServices, use it.
onTransferToolName API call
If the named service wasn't found (or no name was given), and onTransferToolName is configured, your API is called to dynamically return a transfer service.
"default" service
If no transferService was supplied and no API is configured, the entry named "default" in transferServices is used.
Legacy direct number
If the AI passed a transferNumber directly (legacy mode), that number is dialed.
Fail
If none of the above resolve, the AI receives an error response.
Configuring Transfer Services
Transfer services are defined in the transferServices array
inside VoiceElements_Instructions of your CallStart response.
You can define as many named services as you need.
"transferServices": [
{
"name": "default",
"callerId": "+18005551234",
"callerIdName": "Acme Support",
"attended": false,
"useVnsNumberAsCallerId": false,
"dialMode": "Sequential",
"transferNumbers": [
{ "number": "+13035551000", "dialTime": 20 },
{ "number": "+13035551001", "dialTime": 30 },
"+13035559999"
]
},
{
"name": "afterhours",
"callerId": "+18005551234",
"callerIdName": "Acme Support",
"attended": false,
"useVnsNumberAsCallerId": false,
"dialMode": "Simultaneous",
"transferNumbers": [
"+13035552000",
"+13035552001"
]
}
]
Transfer Service Fields
| Field | Type | Description |
|---|---|---|
| name | string | Identifier. "default" is used when the AI calls vns_transfer_call with no service name. |
| callerId | string | E.164 caller ID shown to the recipient. |
| callerIdName | string | Caller ID name shown to the recipient. |
| attended | boolean | false = blind transfer. true = attended transfer (AI announces caller to recipient). |
| useVnsNumberAsCallerId | boolean | If true, uses the called DID as the caller ID instead of callerId. |
| dialMode | string | "Sequential" — try numbers one at a time. "Simultaneous" — ring all at once, first to answer wins. |
| transferNumbers | array | Numbers to dial. Each entry can be a plain string or an object with number and dialTime. |
| attendedTransferSessionUpdate | object | Custom session.update sent to the AI when an attended transfer connects. Overrides the built-in defaults entirely when present. |
| failedTransferSessionUpdate | object | Custom session.update sent to the AI when all transfer attempts fail. |
| preTransferAnnouncement | string | Attended only. Optional caller-facing fallback. See Attended Options. |
| preTransferAnnouncementTimeoutSeconds | integer | null | Attended only. 1–120 seconds; server default is currently 15. |
| attendedDecisionTimeoutSeconds | integer | null | Attended only. 5–300 seconds; server default is currently 30. The consultation decision window. |
| holdMediaReferences | array | Attended only. Up to 10 mediaCatalog references played to the caller while parked. |
Per-Number Dial Time
Each entry in transferNumbers can be a plain string (uses the default 30-second dial time)
or an object with a custom dialTime in seconds.
The last number can have a longer dialTime if you want the call to reach voicemail.
dialTime bounds ringing only — it is not the attended consultation timeout (that's attendedDecisionTimeoutSeconds).
"transferNumbers": [
{ "number": "+13035551000", "dialTime": 12 },
{ "number": "+13035551001", "dialTime": 18 },
"+13035559999"
]
Blind Transfer
Set "attended": false — the caller is immediately bridged to the first available recipient.
The AI is disconnected once the transfer connects.
Sequential
Numbers are tried one at a time. If the first doesn't answer within its dialTime, the next is tried, and so on. If all fail, the failure handler runs.
Simultaneous
All numbers are called at once. The first to answer wins; all other ringing calls are dropped.
{
"name": "support",
"attended": false,
"dialMode": "Sequential",
"transferNumbers": [
{ "number": "+13035551000", "dialTime": 20 },
{ "number": "+13035551001", "dialTime": 20 }
]
}
Attended Transfer
Set "attended": true — the AI is routed to the outgoing call and announces the original caller to the recipient before bridging.
The original caller stays parked — hearing your hold media or generated ringback — until
vns_attended_transfer_accepted completes the route.
When the transfer channel connects, a session.update is sent to the AI
with instructions for the attended transfer phase. The AI then has access to two consultation-only tools, used only while it talks to the destination.
A destination answering does not call either tool and is never treated as acceptance.
vns_attended_transfer_accepted
Call when the recipient agrees to take the call. VNS installs the audio route and the caller is handed off.
vns_attended_transfer_declined
Call when the recipient refuses or voicemail is detected. In sequential mode the next number is tried; otherwise the caller is restored to the AI.
How an Attended Transfer Resolves
- •
transferCallResult: "Connected"means the destination answered. It is not a completed caller handoff. - •The caller remains parked until
vns_attended_transfer_acceptedcompletes the route. - •No answer, busy, error, destination decline, destination disconnect, no decision before
attendedDecisionTimeoutSeconds, setup failure, and every other terminal path except the caller hanging up restore the caller to the AI. - •Sequential mode advances to the next number after an unsuccessful or declined attempt when another configured number remains.
- •Restore is full duplex, and VNS proactively asks the AI to speak to the caller — it does not wait for the caller to talk first.
- •Hold media or generated ringback stops automatically on caller handoff or restore.
- •A
preTransferAnnouncementmust finish playing before the caller is parked.
Default Attended Transfer Instructions
By default, VNS uses the following session.update when the attended transfer connects.
To override it, set attendedTransferSessionUpdate on the transfer service.
{
"type": "session.update",
"session": {
"type": "realtime",
"instructions": "The original caller has requested to be transferred to another number. You have been routed to the outgoing call. When the call is answered take a moment to determine if you reached a voicemail box. If so, then call vns_attended_transfer_declined. If you reach a human, then announce the call to the person that answered. Then ask them to accept the call.\n\n## Tool Call Rules\nYou may only call vns_attended_transfer_accepted or vns_attended_transfer_declined.\n\nvns_attended_transfer_accepted is called when the person answering the transferred call accepts.\n\nvns_attended_transfer_declined is called when the person answering the transferred call refuses the call.",
"tools": [
{
"name": "vns_attended_transfer_accepted",
"description": "Call this function to accept the transfer.",
"type": "function",
"parameters": null
},
{
"name": "vns_attended_transfer_declined",
"description": "Call this function to decline the transfer.",
"type": "function",
"parameters": null
}
]
}
}
Attended Transfer Options
These per-service fields shape what the caller hears and how long VNS waits. All are optional — omit them and attended transfers behave as before (the AI's own transfer speech, generated ringback while parked, and server-default timeouts).
{
"mediaCatalog": [
{ "mediaReference": "HolidayClosureNotice" },
{ "mediaReference": "ReceptionHold" }
],
"transferServices": [
{
"name": "default",
"attended": true,
"dialMode": "Sequential",
"preTransferAnnouncement": "Please stay with me while I connect you.",
"preTransferAnnouncementTimeoutSeconds": 15,
"attendedDecisionTimeoutSeconds": 30,
"holdMediaReferences": ["HolidayClosureNotice", "ReceptionHold"],
"transferNumbers": [
{ "number": "+12085593602", "dialTime": 30 }
],
"attendedTransferSessionUpdate": null,
"failedTransferSessionUpdate": null
}
]
}
| Field | Contract |
|---|---|
| preTransferAnnouncement | Optional caller-facing fallback. VNS plays it only when the model response that requested the transfer did not produce completed caller-facing audio. It is not sent to the model as an instruction. Omit it to keep transfer speech entirely prompt-owned. |
| preTransferAnnouncementTimeoutSeconds | Nullable; valid 1–120 seconds; server default is currently 15. Bounds completed generation plus caller playback before parking. If audible playback can't complete in time, the transfer does not start and the caller stays with the AI. |
| attendedDecisionTimeoutSeconds | Nullable; valid 5–300 seconds; server default is currently 30. The bounded window for the destination to accept or decline during consultation — distinct from transferNumbers[].dialTime, which bounds ringing only. |
| holdMediaReferences | Optional ordered playlist of up to 10 mediaCatalog references; repeats are allowed. It loops through ringing and consultation until bridge or restore. Unusable items are dropped; if no configured item is usable, VNS falls back to generated ringback. |
Transfer Failure Handling
When all transfer attempts fail (all numbers busy/no-answer, or the recipient declines an attended transfer), VoiceElements runs the failure handler. The AI is routed back to the original caller with new session instructions.
Resolution Order
failedTransferSessionUpdate on the TransferService
Use a custom session.update for this specific service.
onTransferFailedToolName
Call your API to decide what to do. Your API returns a TransferFailedResponse JSON object with one of: a retry transferService, a useNamedTransferService name, or a custom sessionUpdate.
TransferFailedDefaults
The built-in fallback, used when nothing above is configured or all return null. The AI apologizes and ends the call.
Built-in Failure Defaults
When no failure handler is configured, VNS routes the AI back with these instructions:
{
"type": "session.update",
"session": {
"type": "realtime",
"instructions": "The call transfer was unsuccessful. Apologize to the caller and let them know the transfer could not be completed at this time. Suggest they try calling back later. Then call vns_end_conversation to end the call.\n\n## Tool Call Rules\nYou must call vns_end_conversation after delivering your message.",
"tools": [
{
"name": "vns_end_conversation",
"description": "Call this function to end the conversation after delivering the transfer failure message.",
"type": "function",
"parameters": {
"type": "object",
"properties": {
"farewell": {
"type": "string",
"description": "The farewell you would like to say to the caller."
}
},
"required": ["farewell"]
}
}
]
}
}
Dynamic Transfer Service via API
If you want your server to decide which transfer service to use at call time — based on time of day, caller history, or queue availability —
set onTransferToolName to a tool name in your toolkit.
When the AI calls vns_transfer_call,
VoiceElements will call your API endpoint and expect a complete TransferService JSON object back.
// In VoiceElements_Instructions:
"onTransferToolName": "transfer_request"
Your endpoint returns a complete TransferService object:
{
"name": "live-agent",
"callerId": "+18005551234",
"callerIdName": "Acme Support",
"attended": false,
"useVnsNumberAsCallerId": false,
"dialMode": "Sequential",
"transferNumbers": [
{ "number": "+13035551000", "dialTime": 20 }
]
}
Additional Configuration Fields
| Field | Description |
|---|---|
| addSecondsToMaxTimeOnTransfer | Extends the maximum call duration by this many seconds once a transfer connects. Default: 7200 (2 hours). |
| onTransferToolName | Tool name to call for dynamic transfer service resolution. Your endpoint returns a full TransferService object. |
| onTransferFailedToolName | Tool name to call when all transfer attempts fail. Your endpoint returns a TransferFailedResponse specifying how to proceed. |
Transfer Evidence in CallEnd
Each transfer attempt appears in the transfers[] array of the
CallEnd webhook.
transferCallResult: "Connected" means the destination leg answered.
It does not prove that the original caller was handed off. Read callerHandoffCompleted and attendedTransferOutcome for the caller-facing result.
Historical Connected values keep this destination-dial meaning.
| Field | Meaning |
|---|---|
| transferCallResult | Destination dial result (for example Connected when the destination answered). |
| transferCallConnect | UTC timestamp when the destination leg connected. |
| attendedTransferOutcome | Terminal caller-facing outcome: CallerHandoffCompleted, RestoredAfterDialFailure, RestoredAfterDestinationDecline, RestoredAfterDestinationDisconnect, RestoredAfterDecisionTimeout, CallerDisconnectedBeforeHandoff, or CallerRestoreFailed; NotApplicable for blind transfers. |
| attendedTransferOutcomeAtUtc | UTC timestamp when the terminal attended outcome was determined. |
| callerHandoffCompleted | true only after every required audio route was installed successfully. |
| callerHandoffAtUtc | UTC timestamp of the completed handoff. |
| callerRestoreOutcome | NotRequired, Completed, or Failed. Describes the telephony route back to the AI. |
| callerRestoredAtUtc | UTC timestamp when the caller/AI route was restored. |
| callerRestoreAudioOutcome | NotRequired, AudioRendered, AudioRenderedResponseIncomplete, NoAudioFromOpenAI, AudioReceivedButNotRendered, ResponseDidNotComplete, FailedBeforeResponse, or UnconfirmedAtCallEnd. |
| callerRestoreAudioOutcomeAtUtc | UTC timestamp when restore-audio evidence became terminal. |
| holdMediaOutcome | NotConfigured, Completed, or FallbackRing (custom hold media couldn't play, so generated ringback was used). |
| holdMediaFailureCode / holdMediaFailureReason | Sanitized diagnostics when custom hold media falls back to generated ringback. |
| holdMediaPlaylist | Items that actually started during this park interval: mediaReference, startedAtUtc, endedAtUtc, and outcome (for example Interrupted when a handoff or restore stopped the item). Never includes paths, URLs, or secrets. |
| holdMediaResumedAtIndex | Playlist index used when playback resumed for the park interval. |
Examples
Both destinations answered ("Connected") — only the second caller was actually handed off. Other CallEnd fields omitted.
Destination declined — caller restored to the AI
{
"transfers": [
{
"transferCalledNumber": "+13035551212",
"transferCallerId": "+13035559999",
"transferCallerIdName": "Business Name",
"transferCallStart": "2026-04-17T16:26:33.2170000Z",
"transferCallEnd": "2026-04-17T16:26:46.8670000Z",
"transferCallConnect": "2026-04-17T16:26:38.2730000Z",
"transferCallResult": "Connected",
"attendedTransferOutcome": "RestoredAfterDestinationDecline",
"attendedTransferOutcomeAtUtc": "2026-04-17T16:26:46.8670000Z",
"callerHandoffCompleted": false,
"callerHandoffAtUtc": null,
"callerRestoreOutcome": "Completed",
"callerRestoredAtUtc": "2026-04-17T16:26:47.0100000Z",
"callerRestoreAudioOutcome": "AudioRendered",
"callerRestoreAudioOutcomeAtUtc": "2026-04-17T16:26:50.2120000Z",
"holdMediaOutcome": "Completed",
"holdMediaFailureReason": null,
"holdMediaFailureCode": null,
"holdMediaPlaylist": [
{
"mediaReference": "TransferHoldMusic",
"startedAtUtc": "2026-04-17T16:26:33.3000000Z",
"endedAtUtc": "2026-04-17T16:26:47.0000000Z",
"outcome": "Interrupted"
}
],
"holdMediaResumedAtIndex": 0
}
]
}
Destination accepted — caller handoff completed
{
"transfers": [
{
"transferCalledNumber": "+13035551212",
"transferCallerId": "+13035559999",
"transferCallerIdName": "Business Name",
"transferCallStart": "2026-09-22T17:10:02.4400000Z",
"transferCallEnd": "2026-09-22T17:16:55.0120000Z",
"transferCallConnect": "2026-09-22T17:10:07.9310000Z",
"transferCallResult": "Connected",
"attendedTransferOutcome": "CallerHandoffCompleted",
"attendedTransferOutcomeAtUtc": "2026-09-22T17:10:29.6070000Z",
"callerHandoffCompleted": true,
"callerHandoffAtUtc": "2026-09-22T17:10:29.6070000Z",
"callerRestoreOutcome": "NotRequired",
"callerRestoredAtUtc": null,
"callerRestoreAudioOutcome": "NotRequired",
"callerRestoreAudioOutcomeAtUtc": null,
"holdMediaOutcome": "NotConfigured",
"holdMediaFailureReason": null,
"holdMediaFailureCode": null,
"holdMediaPlaylist": null
}
]
}
Ready to Automate Your Phone Lines?
Create your account in VnsPortal and set up your first agent from a template today — or book a live demo and we'll walk you through it.
Questions first? Contact sales