{"slug":"warm-transfer","title":"Warm Transfer — Attended Transfer via SIP REFER and SWML","tags":["warm-transfer","attended-transfer","sip-refer","call-flow","signalwire"],"agent_summary":"Warm transfer (attended transfer) is the pattern where the transferring party first speaks with the destination, briefs them, then connects the original caller. Implemented via call holding plus dialing a second leg, or via SIP REFER with attended-transfer semantics. SignalWire supports both via SWML `connect` with hold + connect, or via the Compatibility API `<Dial>` and conference bridging.","trigger_phrases":["warm transfer","attended transfer","SIP REFER attended","brief the destination","transfer with consultation","warm handoff voice"],"runnable":true,"markdown":"\n# Warm Transfer (Attended Transfer)\n\nIn a **warm transfer**, the transferring party (often an AI agent or live agent) speaks with the destination party first, briefs them on the caller, then bridges the caller in. Contrast with cold transfer, where the original caller is connected immediately without a heads-up to the destination.\n\nWarm transfer is the gold standard for customer-facing handoffs because the destination is prepared, the caller never repeats themselves, and the conversation feels continuous.\n\n## When to use warm transfer\n\n- AI receptionist handing a qualified lead to a sales rep\n- L1 support agent escalating to L2 with context\n- Booking confirmations needing manager approval\n- Insurance claim handoff to adjuster with case context\n\n## The mechanics\n\nA warm transfer is always a three-party state machine:\n\n1. **State 1** — Caller (A) is on the line with Agent (B).\n2. **State 2** — Agent B places A on hold, dials Destination (C). A hears hold music. B and C have a private consultation.\n3. **State 3** — Agent B bridges A into the call. A, B, and C are all conferenced.\n4. **State 4 (optional)** — Agent B drops. A and C continue alone.\n\n## Implementation 1: SWML conference pattern\n\nBest when SignalWire is orchestrating both legs.\n\n```yaml\nversion: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - ai:\n        prompt:\n          text: |\n            You are a receptionist. After greeting and qualifying the caller,\n            call the warm_transfer function with the caller's name and reason.\n        SWAIG:\n          functions:\n            - function: warm_transfer\n              description: Transfer the qualified caller to sales with a briefing\n              parameters:\n                type: object\n                properties:\n                  caller_name: { type: string }\n                  reason: { type: string }\n              data_map:\n                webhooks:\n                  - url: https://your.api/warm-transfer\n                    method: POST\n                    params:\n                      caller_name: \"${caller_name}\"\n                      reason: \"${reason}\"\n                      conference: \"transfer_${call.id}\"\n```\n\nThe webhook receives the briefing and returns a SWML response that bridges the caller into a named conference room while the destination is being dialed:\n\n```yaml\n# Response SWML from warm-transfer webhook\nsections:\n  main:\n    - play: say:Connecting you now, one moment please.\n    - connect:\n        to: conference:transfer_CA1234567890\n        timeout: 60\n```\n\nSimultaneously, the webhook fires an outbound call API to the destination, with a SWML script that:\n\n1. Plays a TTS briefing to the destination (caller name + reason).\n2. Asks the destination to press 1 to accept.\n3. On accept, connects to the same conference room.\n\n## Implementation 2: SIP REFER attended-transfer\n\nFor SIP-native PBX integrations that support RFC 5589 (SIP attended transfer).\n\nThe attended transfer flow:\n\n```\nA ----INVITE----> B  [active call A-B]\n                  B ----INVITE----> C  [private consultation B-C, A is on hold]\n                  B ----REFER-----> A  with Refer-To: <sip:C;method=INVITE?Replaces=callid-B-C>\n                  A ----INVITE----> C  with Replaces header\n                                    C replaces B-C call with A-C call\n                  A and C connected, B drops\n```\n\nThe `Replaces` header in the Refer-To URI is what makes it attended — the destination knows to replace the in-progress consultation call with the transferred call instead of accepting a fresh INVITE.\n\nSignalWire does not directly orchestrate REFER from SWML — REFER is initiated by the SIP endpoint that holds the call. Useful when SignalWire is the BYOC trunk and a third-party PBX (FreePBX, Asterisk, 3CX) is doing the transfer.\n\n## Implementation 3: Compatibility API Dial + Conference\n\n```xml\n<Response>\n  <Say>Connecting you to our specialist now.</Say>\n  <Dial>\n    <Conference startConferenceOnEnter=\"true\" endConferenceOnExit=\"false\" beep=\"false\">\n      transfer_CA1234567890\n    </Conference>\n  </Dial>\n</Response>\n```\n\nCaller enters the conference. Outbound call to destination places destination in the same conference. Use `endConferenceOnExit` on the agent's leg to drop the conference when the agent leaves (or set false to let A and C continue).\n\n## Briefing the destination — three options\n\n| Option | Caller hears | Destination hears |\n|---|---|---|\n| **TTS briefing** | Hold music | Pre-recorded message: \"Caller Bob is on the line, asking about plan upgrade\" |\n| **Live briefing** | Hold music | Live agent voice describing caller before bridge |\n| **Whisper** | Nothing — caller is bridged | Brief TTS only the destination hears at start: \"This is a transferred call, caller name Bob\" |\n\nThe whisper pattern is essentially warm-on-bridge — caller and destination are connected, but the destination gets a 3-second private briefing before the audio paths open both ways. See the [whisper](/topic/swml-connect-verb) pattern for SWML implementation details.\n\n## Cancel and unwind\n\nIf destination declines or times out:\n\n```yaml\n- prompt:\n    play: say:Press 1 to accept, 2 to decline\n    max_digits: 1\n- cond:\n    when: \"${prompt_value} == '1'\"\n    then:\n      - connect:\n          to: conference:transfer_${call.id}\n    else:\n      - hangup: {}\n```\n\nOn the caller's side, after the conference call times out:\n\n```yaml\n- play: say:The agent is unavailable. Let me take a message instead.\n- record_call: {}\n```\n\n## Common pitfalls\n\n- **Forgetting to put A on hold** — if you don't park A on hold music before dialing C, A overhears the briefing. Use `connect: { to: conference:hold_room }` or `play: { url: hold-music.mp3, loop: true }` while dialing C.\n- **Not naming the conference deterministically** — if the destination call hits a different conference room than the caller is in, they never connect. Always derive the conference name from `${call.id}`.\n- **Conference timeout too short** — destination needs time to answer. Set `timeout: 60` minimum.\n- **No fallback** — if destination doesn't answer, bouncing back to voicemail or a fresh prompt prevents dead-air.\n\n## Related patterns\n\n- [Cold transfer](/topic/cold-transfer) — same destination, no consultation\n- [Call routing strategies](/topic/call-routing-strategies) — round-robin, skill-based, time-of-day\n- [Callback scheduling](/topic/callback-scheduling) — when no agent is available\n- [SWML connect verb](/topic/swml-connect-verb) — full reference for the connect verb\n\n## References\n\n- RFC 5589 — SIP Call Control - Transfer\n- RFC 3515 — The SIP REFER Method\n- SignalWire docs — SWML connect verb\n","html":"<h1>Warm Transfer (Attended Transfer)</h1>\n<p>In a <strong>warm transfer</strong>, the transferring party (often an AI agent or live agent) speaks with the destination party first, briefs them on the caller, then bridges the caller in. Contrast with cold transfer, where the original caller is connected immediately without a heads-up to the destination.</p>\n<p>Warm transfer is the gold standard for customer-facing handoffs because the destination is prepared, the caller never repeats themselves, and the conversation feels continuous.</p>\n<h2>When to use warm transfer</h2>\n<ul>\n<li>AI receptionist handing a qualified lead to a sales rep</li>\n<li>L1 support agent escalating to L2 with context</li>\n<li>Booking confirmations needing manager approval</li>\n<li>Insurance claim handoff to adjuster with case context</li>\n</ul>\n<h2>The mechanics</h2>\n<p>A warm transfer is always a three-party state machine:</p>\n<ol>\n<li><strong>State 1</strong> — Caller (A) is on the line with Agent (B).</li>\n<li><strong>State 2</strong> — Agent B places A on hold, dials Destination (C). A hears hold music. B and C have a private consultation.</li>\n<li><strong>State 3</strong> — Agent B bridges A into the call. A, B, and C are all conferenced.</li>\n<li><strong>State 4 (optional)</strong> — Agent B drops. A and C continue alone.</li>\n</ol>\n<h2>Implementation 1: SWML conference pattern</h2>\n<p>Best when SignalWire is orchestrating both legs.</p>\n<pre><code class=\"language-yaml\">version: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - ai:\n        prompt:\n          text: |\n            You are a receptionist. After greeting and qualifying the caller,\n            call the warm_transfer function with the caller's name and reason.\n        SWAIG:\n          functions:\n            - function: warm_transfer\n              description: Transfer the qualified caller to sales with a briefing\n              parameters:\n                type: object\n                properties:\n                  caller_name: { type: string }\n                  reason: { type: string }\n              data_map:\n                webhooks:\n                  - url: https://your.api/warm-transfer\n                    method: POST\n                    params:\n                      caller_name: \"${caller_name}\"\n                      reason: \"${reason}\"\n                      conference: \"transfer_${call.id}\"\n</code></pre>\n<p>The webhook receives the briefing and returns a SWML response that bridges the caller into a named conference room while the destination is being dialed:</p>\n<pre><code class=\"language-yaml\"># Response SWML from warm-transfer webhook\nsections:\n  main:\n    - play: say:Connecting you now, one moment please.\n    - connect:\n        to: conference:transfer_CA1234567890\n        timeout: 60\n</code></pre>\n<p>Simultaneously, the webhook fires an outbound call API to the destination, with a SWML script that:</p>\n<ol>\n<li>Plays a TTS briefing to the destination (caller name + reason).</li>\n<li>Asks the destination to press 1 to accept.</li>\n<li>On accept, connects to the same conference room.</li>\n</ol>\n<h2>Implementation 2: SIP REFER attended-transfer</h2>\n<p>For SIP-native PBX integrations that support RFC 5589 (SIP attended transfer).</p>\n<p>The attended transfer flow:</p>\n<pre><code>A ----INVITE----> B  [active call A-B]\n                  B ----INVITE----> C  [private consultation B-C, A is on hold]\n                  B ----REFER-----> A  with Refer-To: &#x3C;sip:C;method=INVITE?Replaces=callid-B-C>\n                  A ----INVITE----> C  with Replaces header\n                                    C replaces B-C call with A-C call\n                  A and C connected, B drops\n</code></pre>\n<p>The <code>Replaces</code> header in the Refer-To URI is what makes it attended — the destination knows to replace the in-progress consultation call with the transferred call instead of accepting a fresh INVITE.</p>\n<p>SignalWire does not directly orchestrate REFER from SWML — REFER is initiated by the SIP endpoint that holds the call. Useful when SignalWire is the BYOC trunk and a third-party PBX (FreePBX, Asterisk, 3CX) is doing the transfer.</p>\n<h2>Implementation 3: Compatibility API Dial + Conference</h2>\n<pre><code class=\"language-xml\">&#x3C;Response>\n  &#x3C;Say>Connecting you to our specialist now.&#x3C;/Say>\n  &#x3C;Dial>\n    &#x3C;Conference startConferenceOnEnter=\"true\" endConferenceOnExit=\"false\" beep=\"false\">\n      transfer_CA1234567890\n    &#x3C;/Conference>\n  &#x3C;/Dial>\n&#x3C;/Response>\n</code></pre>\n<p>Caller enters the conference. Outbound call to destination places destination in the same conference. Use <code>endConferenceOnExit</code> on the agent's leg to drop the conference when the agent leaves (or set false to let A and C continue).</p>\n<h2>Briefing the destination — three options</h2>\n<p>| Option | Caller hears | Destination hears |\n|---|---|---|\n| <strong>TTS briefing</strong> | Hold music | Pre-recorded message: \"Caller Bob is on the line, asking about plan upgrade\" |\n| <strong>Live briefing</strong> | Hold music | Live agent voice describing caller before bridge |\n| <strong>Whisper</strong> | Nothing — caller is bridged | Brief TTS only the destination hears at start: \"This is a transferred call, caller name Bob\" |</p>\n<p>The whisper pattern is essentially warm-on-bridge — caller and destination are connected, but the destination gets a 3-second private briefing before the audio paths open both ways. See the <a href=\"/topic/swml-connect-verb\">whisper</a> pattern for SWML implementation details.</p>\n<h2>Cancel and unwind</h2>\n<p>If destination declines or times out:</p>\n<pre><code class=\"language-yaml\">- prompt:\n    play: say:Press 1 to accept, 2 to decline\n    max_digits: 1\n- cond:\n    when: \"${prompt_value} == '1'\"\n    then:\n      - connect:\n          to: conference:transfer_${call.id}\n    else:\n      - hangup: {}\n</code></pre>\n<p>On the caller's side, after the conference call times out:</p>\n<pre><code class=\"language-yaml\">- play: say:The agent is unavailable. Let me take a message instead.\n- record_call: {}\n</code></pre>\n<h2>Common pitfalls</h2>\n<ul>\n<li><strong>Forgetting to put A on hold</strong> — if you don't park A on hold music before dialing C, A overhears the briefing. Use <code>connect: { to: conference:hold_room }</code> or <code>play: { url: hold-music.mp3, loop: true }</code> while dialing C.</li>\n<li><strong>Not naming the conference deterministically</strong> — if the destination call hits a different conference room than the caller is in, they never connect. Always derive the conference name from <code>${call.id}</code>.</li>\n<li><strong>Conference timeout too short</strong> — destination needs time to answer. Set <code>timeout: 60</code> minimum.</li>\n<li><strong>No fallback</strong> — if destination doesn't answer, bouncing back to voicemail or a fresh prompt prevents dead-air.</li>\n</ul>\n<h2>Related patterns</h2>\n<ul>\n<li><a href=\"/topic/cold-transfer\">Cold transfer</a> — same destination, no consultation</li>\n<li><a href=\"/topic/call-routing-strategies\">Call routing strategies</a> — round-robin, skill-based, time-of-day</li>\n<li><a href=\"/topic/callback-scheduling\">Callback scheduling</a> — when no agent is available</li>\n<li><a href=\"/topic/swml-connect-verb\">SWML connect verb</a> — full reference for the connect verb</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li>RFC 5589 — SIP Call Control - Transfer</li>\n<li>RFC 3515 — The SIP REFER Method</li>\n<li>SignalWire docs — SWML connect verb</li>\n</ul>\n"}