SIP Transfers

In a SIP transfer, the party transferring the call doesn't dial the transfer destination. Instead, it sends a REFER asking the SIP peer on the other end of the call (the transferee) to dial it, and that new call replaces the current one. This guide explains how that works when a Guava agent transfers a call, and what it means for your transfer destinations.

A basic call setup

Let's start by reviewing a basic call setup. Say you have an ITSP (like Twilio Elastic SIP) that converts inbound PSTN calls to SIP calls. These calls are forwarded to Guava's SIP trunk and land on a specific Guava agent.

ITSPGuavaTransfer DestinationREFERThe ITSP forwards the call to Guava.Guava sends a REFER to the ITSP.Refer-To: sip:support@pbx.example.comThe call to Guava ends.The ITSP dials the transfer destination.

From Guava's point of view, the ITSP is the SIP peer. Every SIP message for this call is exchanged between Guava and the ITSP.

What happens when you transfer

When the agent calls call.transfer(), Guava sends a SIP REFER to the ITSP. The Refer-To header contains your transfer destination.

call.transfer("sip:support@pbx.example.com", "Let the caller know you're transferring them to support.")
Guava ──REFER (Refer-To: sip:support@pbx.example.com)──▶ ITSP
ITSP  ──INVITE──▶ sip:support@pbx.example.com
ITSP  bridges the caller to the new call, and Guava leaves the call

Phone numbers work the same way. A destination like +18005550199 becomes a tel: URI in the Refer-To header, and the ITSP dials the number.

# Sends REFER with Refer-To: tel:+18005550199
call.transfer("+18005550199")
Key point: The refer destination is not dialed from Guava. It's dialed by the side that receives the REFER, so the transfer destination must be routable from the transferee.

Choosing a destination

Since the ITSP places the new call, pick destinations that the ITSP can reach:

# Works: a public SIP URI on your PBX that the ITSP can reach.
call.transfer("sip:support@pbx.example.com")

# Works: another Guava agent, since the ITSP can already reach Guava's SIP trunk.
call.transfer("sip:guavasip-xxx@sip.goguava.ai")

# Works if the ITSP allows PSTN transfers.
call.transfer("+18005550199")

The ITSP also decides whether to accept the REFER at all. Some ITSPs require REFER to be enabled, or limit which destinations they will dial. Please see your ITSP provider's docs for more information.

Sending information to the transfer destination

The transfer URI can carry extra data for the transfer destination, such as the caller's account ID or why they're calling. There are two ways to attach it, and which one to use depends on the system you're transferring to.

Tip: You can use the SipUri helper to build your transfer destination URI. It automatically handles escaping in header and URI params. You can also build the URI string yourself.

URI parameters are appended to the URI after a ;. The ITSP dials the URI with the parameters included, so the destination can read them from the Request-URI of the incoming INVITE.

from guava.sip import SipUri

# INVITE sip:support@pbx.example.com;queue=billing;account=12345
call.transfer(SipUri(
  user="support",
  host="pbx.example.com",
  uri_params={"queue": "billing", "account": "12345"},
))

Header parameters are appended after a ?, with multiple headers joined by &. The ITSP adds each one as a SIP header on the INVITE it sends to the destination. Use the X- prefix for custom headers.

from guava.sip import SipUri

# INVITE sip:support@pbx.example.com
# X-Account-Id: 12345
# X-Reason: <reason_for_calling>
call.transfer(SipUri(
  user="support",
  host="pbx.example.com",
  headers={
      "X-Account-Id": "12345",
      "X-Reason": call.get_field("reason_for_calling"),
  },
))

These options only apply to sip: destinations, not phone numbers.

Note: The ITSP builds the new INVITE, so it decides which parameters and headers reach the destination. Many ITSPs drop header parameters, or only keep X- headers. Check your ITSP's docs before relying on either.

User-to-User (UUI) information

Many contact center platforms and PBXs expect call data in the standard User-to-User header (RFC 7433) rather than in custom X- headers. You can send it as a header parameter like any other header. The value is usually hex-encoded, followed by an encoding=hex parameter.

from guava.sip import SipUri

uui = "account=12345".encode().hex()

# INVITE sip:support@pbx.example.com
# User-to-User: 6163636f756e743d3132333435;encoding=hex
call.transfer(SipUri(
  user="support",
  host="pbx.example.com",
  headers={"User-to-User": f"{uui};encoding=hex"},
))

Include ;encoding=hex in the header value. SipUri URL-encodes it along with the rest of the value, since ; and = aren't allowed unescaped in a URI header value.

The format of the payload itself is up to the receiving system, so check what it expects. Some systems also limit the payload size, so keep it short.

Questions? hi@goguava.ai