Skip to main content
Version: 0.9.17-patch1

Call transfer and termination behavior

Agent handover transfers the active call to another party via SIP REFER (or, optionally, SIP INVITE). Call termination sends carrier-facing SIP and Q.850 reasons and may include configured or bot-provided BYE headers. This page describes both paths, their enforced SIP header limits, and their failure behavior. See also SIP signaling reference, SIP trunk log reference for inbound SIP trunk health messages, and SIP header context rules.

SIP header limits (INVITE / REFER)

For INVITE and REFER transfers, custom SIP headers are validated at three layers — the runtime-command zod schema, the action resolver, and the egress-intent publish step — and rejected when out of budget (never silently truncated). Hangup uses the same limits but drops over-budget optional headers so the call can still end.

LimitValueSource
Max optional bot/configured headers47SIP_HEADER_MAX_COUNT
Complete SIP message header ceiling64AUDIOCODES_SIP_MAX_HEADERS
Reserved standard/platform headers16 + ReasonSIP_STANDARD_PLATFORM_HEADER_COUNT + Reason slot
Max header name length128 charsSIP_HEADER_MAX_NAME_CHARS
Max header value length4096 charsAUDIOCODES_SIP_MAX_HEADER_VALUE_CHARS
Max single header line (name + : + value + CRLF)4096 charsAUDIOCODES_SIP_MAX_HEADER_CHARS
Max total custom headers (sum of name + value chars)12,000 charsSIP_HEADER_MAX_TOTAL_CHARS
Estimated total SIP message (custom + ~768-char platform buffer)65,000 charsAUDIOCODES_SIP_MAX_MESSAGE_CHARS
JSON request body1 MBFastify default

The line limit is the tighter single-header bound: a 4,096-character value plus a non-empty name fails the 4,096-character line check (name + : + value + CRLF).

RFC 3261 §18.1.1's 1,300-byte UDP-safe figure is emitted as a warning only — oversized requests are expected to travel over TCP/TLS and are not rejected on that basis alone.

Platform termination signaling

Delphi owns the mapping from application causes to numeric SIP and Q.850 causes. Bot and flow input can provide diagnostic text but cannot directly set the numeric values. The selected cause is used consistently for the platform SIP header, Asterisk hangup reason, and downstream Q.850 signaling.

Platform causeCategorySIP causeQ.850 causeTypical use
conversation_completeSystem20016Normal bot or flow completion.
bot_timeoutBotSide408102Bot/no-input timeout.
stt_error / tts_errorSpeechService50341Speech provider timeout or failure.
internal_failureError50041Internal, setup, Redis, or provider-init error.
policy_rejectedClientSide40321Policy, subscription, or permission rejection.
unknown_numberClientSide4041Unknown or invalid destination/extension.
busyClientSide48617Busy destination.
handoverSystem20016Transfer, reroute, or escalation.

Reason text has control characters replaced with spaces, repeated whitespace collapsed, and quotes/backslashes escaped for an RFC quoted string. It is limited to 256 characters; a blank value falls back to the platform cause name.

Signaling by call path

Call pathCarrier-facing signaling
Answered call endsThe BYE contains Asterisk's Q.850 Reason and Delphi's SIP Reason. TelPro preserves the Q.850 value and appends the platform SIP value.
Unanswered inbound call is rejectedThe platform SIP Reason is added to the final INVITE response because no BYE exists yet.
In-dialog transfer uses SIP REFERThe REFER contains the platform SIP Reason.
Transfer creates a new outbound SIP INVITEThe new dialog contains X-Transfer-Reason: <cause-id>;sip-cause=<n>;q850-cause=<n>;text="<text>" instead of reusing a Reason header from the original dialog.

RFC 3326 permits multiple Reason values. A typical answered termination can therefore contain both:

Reason: Q.850;cause=16
Reason: SIP;cause=200;text="BotConversationCompleted"

Bot hangup header propagation

An external managed bot can supply hangupSipHeaders in the canonical {name, value} action envelope or the SeeTest/mock-bot {eventName, input} envelope. See Managed callback actions for payload fields and examples.

For an external bot hangup, Delphi resolves optional BYE headers as follows:

  1. Headers configured in Flow Builder form the base set.
  2. Bot hangupSipHeaders are added to that set.
  3. When names collide case-insensitively, the bot value wins for that call.
  4. An omitted or empty bot array leaves configured headers in place.
  5. The resolved optional headers are sent with the platform-generated Reason on the same BYE.

This additive behavior applies only to external managed-bot hangups. Explicit header arrays from flow-engine failover, SDK, API, and sandbox/tool hangups keep their existing replacement semantics.

Bot headers cannot replace protected routing or dialog headers such as Via, From, To, Call-ID, CSeq, Route, or Reason. Invalid header-name characters and control characters in values are sanitized before the final wire-level budget check.

Hangup is fail-open

Optional BYE headers must not keep a call connected:

  • If configured headers plus bot additions exceed a hard budget, the complete bot set is dropped and a valid configured set is retained.
  • If the configured set is itself invalid, all optional headers are dropped.
  • If outbound header rules make the final set exceed a budget, the optional custom set is dropped at the final wire check.
  • The platform Reason is generated and validated separately, so it does not consume the 47 optional-header slots.
  • A Redis or TelPro header-intent publication failure is logged but does not block hangup or transfer execution.

The call still disconnects after any optional-header drop. Transfer remains fail-closed: an over-budget transfer header set is rejected before carrier-facing SIP signaling.

Operator signals

SignalMeaning
termination_signal_selectedTelPhi selected the platform cause and SIP/Q.850 values.
transfer_signal_selectedTelPhi selected the platform handover reason for REFER or INVITE.
sip_egress_intent_publishedCarrier-facing headers were published for BYE, REFER, INVITE, or the final response.
sip_egress_intent_failedHeader publication failed; call termination or transfer continued fail-open.
optional_termination_headers_sanitizedInvalid optional header names or values were normalized or removed.
optional_termination_headers_droppedOptional platform-path headers exceeded the budget; only the platform reason was kept.
hangup_headers_droppedBot/configured BYE headers exceeded a budget; hangup continued without the dropped set.
hangup_successThe channel disconnected successfully.

Transfer target fallback

A bot transfer or handover event may omit transferTarget. When it does, the gateway resolves the target with this precedence:

  1. Bot overridetransferTarget (or target) on the event.
  2. Managed-action defaultgatewayProfile.actions.transfer.target, configured in the flow's managed transfer inspector (TelWeb → flow → managed transfer action → Default target).
  3. Hard error — Transfer requires a target: the bot omitted transferTarget and no default target is configured in the flow managed transfer action.

The fallback code path is wired into every dispatch path (external bot, TOBi, sandbox tool, and flow engine). Two things are worth noting:

  • A target set on a flow transfer node is used by the flow engine's transfer node only. It is not consulted by bot-driven handovers.
  • The managed transfer action default target is what bot-driven handovers fall back to. If it is empty, the handover fails with the error above.

To avoid the failure, set the Default target in the managed transfer action, or always send transferTarget from the bot. TelWeb emits a non-blocking readiness warning when a non-sip_bye managed transfer is enabled but has no default target.

Custom SIP headers on transfer

Two behaviors determine which headers reach the transfer request:

  1. Bot headers replace flow defaults — they are not merged. transferSipHeaders from the bot and sipHeaders from the flow default are resolved with a first-defined policy: if the bot sends any headers, the flow default headers are dropped (and vice versa). To combine them, use header context rules (headerRules) on the flow default — rules are an allowlist/transform over call metadata plus explicit headers, so bot headers pass through only when a rule matches.
  2. For REFER, custom headers travel via a Redis Call-ID index, not as channel-stamped headers. They appear in the sip_egress_intent_published and sip_headers_prepared log events (component call_action_service) under headerNames / headerCount — not as PJSIP headers on the Stasis channel. This is by design: REFER stays in the existing SIP dialog, so TelPro resolves the action headers by SIP Call-ID.

If configured headers are absent from the egress intent, check whether the bot also sent transferSipHeaders (which replaces them) and whether headerRules are configured on the flow default.

Transfer failure logging

Two failure paths surface in the call log:

  • Missing or blank target: resolveTransferAction throws a CallActionValidationError whose message distinguishes "the bot omitted transferTarget and no default configured" from "the bot sent a blank target". The runtime-command dispatcher records this as a runtime_command_failed action on the conversation.
  • SIP header budget exceeded: the egress intent is not published and a call_action_service / transfer_blocked (status error) log is emitted with code sip_header_budget_exceeded, the method, target, and header count.

REFER transfer outcome

After dispatching the REFER, TelPhi polls the ARI channel variable VAI_REFER_TRANSFER_STATUS for up to 5 s (100 ms cadence). On a fast successful handoff, the channel transfers away or tears down before the variable is readable, so the status is observed as undefined.

A blind REFER is fire-and-forget: "dispatched" is success. The status mapper treats an unobserved result as success in blind mode (SIP REFER dispatched (blind), refer_transfer_dispatched / status: success). Explicit local dispatch failures (TRANSFERSTATUS = FAILURE / MISCONF) still surface as real errors.

In notify mode an unobserved result is a genuine failure, because notify expects a REFER NOTIFY that reports the downstream answer. To get the true far-end answer/decline outcome (and bot regain-control on failure), configure referOutcomeMode: notify + transferNotifications: true.

Downstream reject behavior

Downstream response codes are mapped by mapReferHandoverStatus:

SIP responseMapped status
486 Busy Here / 603 Declinedeclined
480 Temporarily UnavailablenotAvailable
408 Request Timeouttimeout
404 Not FoundnotReachable

With the default referOutcomeMode: blind, the handoff is fire-and-forget: the bot leg is disconnected regardless of the downstream outcome, so the caller hears the downstream busy/decline tone.

To let the bot regain control on a rejected transfer (e.g. play a fallback prompt or try a second target), configure referOutcomeMode: notify together with transferNotifications: true. This requests Refer-Sub: true so a provider that supports REFER NOTIFY can report the downstream outcome, and the bot leg is kept alive on failure.

Handover reason

handoverReason (aliased with hangupReason) is optional. It is mapped to the transfer reason field. The carrier-facing header depends on the transfer method:

MethodReason signaling
sip_bye disconnect-reconnectSIP Reason on the BYE.
sip_refer / sip_refer_replacePlatform SIP Reason on the REFER.
sip_invite and other INVITE transfersX-Transfer-Reason on the new outbound dialog.

When the reason is absent, Delphi uses handover. Numeric causes remain platform-controlled (SIP 200 / Q.850 16 for the handover cause).