Developer Guide / Transfer Call Options

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:

1

Named service

If transferService is provided and matches a name in transferServices, use it.

2

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.

3

"default" service

If no transferService was supplied and no API is configured, the entry named "default" in transferServices is used.

4

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_accepted completes 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 preTransferAnnouncement must 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

1

failedTransferSessionUpdate on the TransferService

Use a custom session.update for this specific service.

2

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.

3

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
transferCallResultDestination dial result (for example Connected when the destination answered).
transferCallConnectUTC timestamp when the destination leg connected.
attendedTransferOutcomeTerminal caller-facing outcome: CallerHandoffCompleted, RestoredAfterDialFailure, RestoredAfterDestinationDecline, RestoredAfterDestinationDisconnect, RestoredAfterDecisionTimeout, CallerDisconnectedBeforeHandoff, or CallerRestoreFailed; NotApplicable for blind transfers.
attendedTransferOutcomeAtUtcUTC timestamp when the terminal attended outcome was determined.
callerHandoffCompletedtrue only after every required audio route was installed successfully.
callerHandoffAtUtcUTC timestamp of the completed handoff.
callerRestoreOutcomeNotRequired, Completed, or Failed. Describes the telephony route back to the AI.
callerRestoredAtUtcUTC timestamp when the caller/AI route was restored.
callerRestoreAudioOutcomeNotRequired, AudioRendered, AudioRenderedResponseIncomplete, NoAudioFromOpenAI, AudioReceivedButNotRendered, ResponseDidNotComplete, FailedBeforeResponse, or UnconfirmedAtCallEnd.
callerRestoreAudioOutcomeAtUtcUTC timestamp when restore-audio evidence became terminal.
holdMediaOutcomeNotConfigured, Completed, or FallbackRing (custom hold media couldn't play, so generated ringback was used).
holdMediaFailureCode / holdMediaFailureReasonSanitized diagnostics when custom hold media falls back to generated ringback.
holdMediaPlaylistItems 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.
holdMediaResumedAtIndexPlaylist 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