T
Telephony SOPKnowledge Base
Search
← All topics

Warm Transfer — Attended Transfer via SIP REFER and SWML

runnable

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.

warm-transferattended-transfersip-refercall-flowsignalwire
Agent trigger phrases: warm transfer · attended transfer · SIP REFER attended · brief the destination · transfer with consultation · warm handoff voice

Warm Transfer (Attended Transfer)

In 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.

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.

When to use warm transfer

  • AI receptionist handing a qualified lead to a sales rep
  • L1 support agent escalating to L2 with context
  • Booking confirmations needing manager approval
  • Insurance claim handoff to adjuster with case context

The mechanics

A warm transfer is always a three-party state machine:

  1. State 1 — Caller (A) is on the line with Agent (B).
  2. State 2 — Agent B places A on hold, dials Destination (C). A hears hold music. B and C have a private consultation.
  3. State 3 — Agent B bridges A into the call. A, B, and C are all conferenced.
  4. State 4 (optional) — Agent B drops. A and C continue alone.

Implementation 1: SWML conference pattern

Best when SignalWire is orchestrating both legs.

version: 1.0.0
sections:
  main:
    - answer: {}
    - ai:
        prompt:
          text: |
            You are a receptionist. After greeting and qualifying the caller,
            call the warm_transfer function with the caller's name and reason.
        SWAIG:
          functions:
            - function: warm_transfer
              description: Transfer the qualified caller to sales with a briefing
              parameters:
                type: object
                properties:
                  caller_name: { type: string }
                  reason: { type: string }
              data_map:
                webhooks:
                  - url: https://your.api/warm-transfer
                    method: POST
                    params:
                      caller_name: "${caller_name}"
                      reason: "${reason}"
                      conference: "transfer_${call.id}"

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:

# Response SWML from warm-transfer webhook
sections:
  main:
    - play: say:Connecting you now, one moment please.
    - connect:
        to: conference:transfer_CA1234567890
        timeout: 60

Simultaneously, the webhook fires an outbound call API to the destination, with a SWML script that:

  1. Plays a TTS briefing to the destination (caller name + reason).
  2. Asks the destination to press 1 to accept.
  3. On accept, connects to the same conference room.

Implementation 2: SIP REFER attended-transfer

For SIP-native PBX integrations that support RFC 5589 (SIP attended transfer).

The attended transfer flow:

A ----INVITE----> B  [active call A-B]
                  B ----INVITE----> C  [private consultation B-C, A is on hold]
                  B ----REFER-----> A  with Refer-To: <sip:C;method=INVITE?Replaces=callid-B-C>
                  A ----INVITE----> C  with Replaces header
                                    C replaces B-C call with A-C call
                  A and C connected, B drops

The 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.

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.

Implementation 3: Compatibility API Dial + Conference

<Response>
  <Say>Connecting you to our specialist now.</Say>
  <Dial>
    <Conference startConferenceOnEnter="true" endConferenceOnExit="false" beep="false">
      transfer_CA1234567890
    </Conference>
  </Dial>
</Response>

Caller 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).

Briefing the destination — three options

| Option | Caller hears | Destination hears | |---|---|---| | TTS briefing | Hold music | Pre-recorded message: "Caller Bob is on the line, asking about plan upgrade" | | Live briefing | Hold music | Live agent voice describing caller before bridge | | Whisper | Nothing — caller is bridged | Brief TTS only the destination hears at start: "This is a transferred call, caller name Bob" |

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 whisper pattern for SWML implementation details.

Cancel and unwind

If destination declines or times out:

- prompt:
    play: say:Press 1 to accept, 2 to decline
    max_digits: 1
- cond:
    when: "${prompt_value} == '1'"
    then:
      - connect:
          to: conference:transfer_${call.id}
    else:
      - hangup: {}

On the caller's side, after the conference call times out:

- play: say:The agent is unavailable. Let me take a message instead.
- record_call: {}

Common pitfalls

  • 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.
  • 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}.
  • Conference timeout too short — destination needs time to answer. Set timeout: 60 minimum.
  • No fallback — if destination doesn't answer, bouncing back to voicemail or a fresh prompt prevents dead-air.

Related patterns

References

  • RFC 5589 — SIP Call Control - Transfer
  • RFC 3515 — The SIP REFER Method
  • SignalWire docs — SWML connect verb