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 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.
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.
| Limit | Value | Source |
|---|---|---|
| Max optional bot/configured headers | 47 | SIP_HEADER_MAX_COUNT |
| Complete SIP message header ceiling | 64 | AUDIOCODES_SIP_MAX_HEADERS |
| Reserved standard/platform headers | 16 + Reason | SIP_STANDARD_PLATFORM_HEADER_COUNT + Reason slot |
| Max header name length | 128 chars | SIP_HEADER_MAX_NAME_CHARS |
| Max header value length | 4096 chars | AUDIOCODES_SIP_MAX_HEADER_VALUE_CHARS |
Max single header line (name + : + value + CRLF) | 4096 chars | AUDIOCODES_SIP_MAX_HEADER_CHARS |
| Max total custom headers (sum of name + value chars) | 12,000 chars | SIP_HEADER_MAX_TOTAL_CHARS |
| Estimated total SIP message (custom + ~768-char platform buffer) | 65,000 chars | AUDIOCODES_SIP_MAX_MESSAGE_CHARS |
| JSON request body | 1 MB | Fastify 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 cause | Category | SIP cause | Q.850 cause | Typical use |
|---|---|---|---|---|
conversation_complete | System | 200 | 16 | Normal bot or flow completion. |
bot_timeout | BotSide | 408 | 102 | Bot/no-input timeout. |
stt_error / tts_error | SpeechService | 503 | 41 | Speech provider timeout or failure. |
internal_failure | Error | 500 | 41 | Internal, setup, Redis, or provider-init error. |
policy_rejected | ClientSide | 403 | 21 | Policy, subscription, or permission rejection. |
unknown_number | ClientSide | 404 | 1 | Unknown or invalid destination/extension. |
busy | ClientSide | 486 | 17 | Busy destination. |
handover | System | 200 | 16 | Transfer, 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 path | Carrier-facing signaling |
|---|---|
| Answered call ends | The 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 rejected | The platform SIP Reason is added to the final INVITE response because no BYE exists yet. |
| In-dialog transfer uses SIP REFER | The REFER contains the platform SIP Reason. |
| Transfer creates a new outbound SIP INVITE | The 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"
Custom SIP headers on transfer
Explicit header arrays supplied by a flow transfer node replace the configured header set for that action. For REFER, TelPro resolves the action headers through the SIP Call-ID index; inspect the sip_egress_intent_published and sip_headers_prepared events.
The resolved optional headers must fit the budgets above. An oversized set blocks the transfer and logs transfer_blocked with sip_header_budget_exceeded. Use Transfer failure logging to investigate.
Transfer failure logging
A transfer that exceeds the optional-header budget fails before dispatch:
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(statuserror) log is emitted with codesip_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 response | Mapped status |
|---|---|
| 486 Busy Here / 603 Decline | declined |
| 480 Temporarily Unavailable | notAvailable |
| 408 Request Timeout | timeout |
| 404 Not Found | notReachable |
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:
| Method | Reason signaling |
|---|---|
sip_bye disconnect-reconnect | SIP Reason on the BYE. |
sip_refer / sip_refer_replace | Platform SIP Reason on the REFER. |
sip_invite and other INVITE transfers | X-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).