> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goguava.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SIP Transfers

> How SIP REFER transfers work and how to choose transfer destinations

export const SipTransferAnimation = () => {
  const VIEWBOX_X = 32;
  const VIEWBOX_Y = 32;
  const VIEWBOX_WIDTH = 576;
  const VIEWBOX_HEIGHT = 216;
  const ANIMATION_DURATION = 15;
  const ACTOR_RADIUS = 8;
  const ACTOR_LABEL_FONT_SIZE = 11;
  const ITSP = {
    x: 40,
    y: 110,
    width: 130,
    height: 70
  };
  const GUAVA = {
    x: 450,
    y: 40,
    width: 150,
    height: 60
  };
  const DESTINATION = {
    x: 450,
    y: 180,
    width: 150,
    height: 60
  };
  const JUNCTION_X = 310;
  const ITSP_RIGHT = ITSP.x + ITSP.width;
  const GUAVA_LEG_Y = ITSP.y + 22;
  const DESTINATION_LEG_Y = ITSP.y + ITSP.height - 22;
  const GUAVA_CENTER_Y = GUAVA.y + GUAVA.height / 2;
  const DESTINATION_CENTER_Y = DESTINATION.y + DESTINATION.height / 2;
  const ARROW_SIZE = 6;
  const GUAVA_LEG_PATH = `M ${ITSP_RIGHT} ${GUAVA_LEG_Y} H ${JUNCTION_X} V ${GUAVA_CENTER_Y} H ${GUAVA.x - ARROW_SIZE}`;
  const DESTINATION_LEG_PATH = `M ${ITSP_RIGHT} ${DESTINATION_LEG_Y} H ${JUNCTION_X} V ${DESTINATION_CENTER_Y} H ${DESTINATION.x - ARROW_SIZE}`;
  const REFER_WIDTH = 52;
  const REFER_HEIGHT = 18;
  const REFER_START = 2.6;
  const REFER_END = 5.2;
  const CUT_AT = 5.6;
  const DIAL_START = 7.6;
  const DIAL_END = 9;
  const pct = seconds => `${(seconds / ANIMATION_DURATION * 100).toFixed(2)}%`;
  const REFER_POINTS = [[GUAVA.x - REFER_WIDTH / 2 - 4, GUAVA_CENTER_Y], [JUNCTION_X, GUAVA_CENTER_Y], [JUNCTION_X, GUAVA_LEG_Y], [ITSP_RIGHT + REFER_WIDTH / 2 + 4, GUAVA_LEG_Y]];
  const referKeyframes = () => {
    const lengths = REFER_POINTS.slice(1).map(([x, y], i) => {
      const [px, py] = REFER_POINTS[i];
      return Math.abs(x - px) + Math.abs(y - py);
    });
    const total = lengths.reduce((a, b) => a + b, 0);
    let elapsed = REFER_START;
    const stops = REFER_POINTS.map(([x, y], i) => {
      if (i > 0) elapsed += (REFER_END - REFER_START) * lengths[i - 1] / total;
      return `${pct(elapsed)} { transform: translate(${x}px, ${y}px); opacity: 1; }`;
    });
    const [startX, startY] = REFER_POINTS[0];
    const [endX, endY] = REFER_POINTS[REFER_POINTS.length - 1];
    return `
      @keyframes st-refer {
        0%, ${pct(REFER_START - 0.3)} { transform: translate(${startX}px, ${startY}px); opacity: 0; }
        ${stops.join("\n        ")}
        ${pct(CUT_AT)}, 100% { transform: translate(${endX}px, ${endY}px); opacity: 0; }
      }
    `;
  };
  const captionKeyframes = (name, start, end) => {
    const visibleUntil = end === null ? "90%" : pct(end - 0.3);
    const hiddenFrom = end === null ? "97%" : pct(end);
    return `
      @keyframes ${name} {
        0%${start === 0 ? "" : `, ${pct(start)}`} { opacity: 0; }
        ${pct(start + 0.3)}, ${visibleUntil} { opacity: 1; }
        ${hiddenFrom}, 100% { opacity: 0; }
      }
      .${name} { animation: ${name} ${ANIMATION_DURATION}s ease-in-out infinite; }
    `;
  };
  const STYLES = `
    @keyframes st-guava-leg {
      0% { opacity: 0; }
      ${pct(0.5)}, ${pct(CUT_AT)} { opacity: 1; }
      ${pct(CUT_AT + 0.5)}, 90% { opacity: 0.12; }
      97%, 100% { opacity: 0; }
    }
    .st-guava-leg { animation: st-guava-leg ${ANIMATION_DURATION}s ease-in-out infinite; }

    @keyframes st-cut {
      0%, ${pct(CUT_AT)} { opacity: 0; }
      ${pct(CUT_AT + 0.3)}, 90% { opacity: 1; }
      97%, 100% { opacity: 0; }
    }
    .st-cut { animation: st-cut ${ANIMATION_DURATION}s ease-in-out infinite; }

    ${referKeyframes()}
    .st-refer { opacity: 0; animation: st-refer ${ANIMATION_DURATION}s linear infinite; }

    @keyframes st-destination-leg {
      0%, ${pct(DIAL_START)} { stroke-dashoffset: 1; opacity: 1; }
      ${pct(DIAL_END)}, 90% { stroke-dashoffset: 0; opacity: 1; }
      97%, 100% { stroke-dashoffset: 0; opacity: 0; }
    }
    .st-destination-leg { stroke-dasharray: 1; animation: st-destination-leg ${ANIMATION_DURATION}s ease-in-out infinite; }

    @keyframes st-destination-arrow {
      0%, ${pct(DIAL_END - 0.1)} { opacity: 0; }
      ${pct(DIAL_END)}, 90% { opacity: 1; }
      97%, 100% { opacity: 0; }
    }
    .st-destination-arrow { animation: st-destination-arrow ${ANIMATION_DURATION}s ease-in-out infinite; }

    ${captionKeyframes("st-caption-1", 0, REFER_START)}
    ${captionKeyframes("st-caption-2", REFER_START, CUT_AT)}
    ${captionKeyframes("st-caption-3", CUT_AT, DIAL_START)}
    ${captionKeyframes("st-caption-4", DIAL_START, null)}
    .st-caption { opacity: 0; }
  `;
  const Actor = ({box, label, accent}) => <g>
      <rect x={box.x} y={box.y} width={box.width} height={box.height} rx={ACTOR_RADIUS} fill="#272728" stroke={accent ? "rgba(26,147,254,0.4)" : "rgba(255,255,255,0.15)"} strokeWidth="1.5" />
      <text x={box.x + box.width / 2} y={box.y + box.height / 2 + ACTOR_LABEL_FONT_SIZE / 2 - 1} textAnchor="middle" fill={accent ? "#1a93fe" : "#dadada"} fontSize={ACTOR_LABEL_FONT_SIZE} fontFamily="ui-monospace, monospace" fontWeight="700">
        {label}
      </text>
    </g>;
  const Arrowhead = ({x, y, fill}) => <polygon points={`${x + ARROW_SIZE},${y} ${x},${y - ARROW_SIZE / 2 - 1} ${x},${y + ARROW_SIZE / 2 + 1}`} fill={fill} />;
  const Caption = ({animationClass, lines}) => <text className={`st-caption ${animationClass}`} x={ITSP.x} y={52} fill="#acacac" fontSize="10" fontFamily="ui-monospace, monospace">
      {lines.map((line, i) => <tspan key={i} x={ITSP.x} dy={i === 0 ? 0 : "1.5em"} fill={i === 0 ? "#dadada" : "#777779"}>{line}</tspan>)}
    </text>;
  return <svg viewBox={`${VIEWBOX_X} ${VIEWBOX_Y} ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`} xmlns="http://www.w3.org/2000/svg" className="w-full my-8">
      <defs>
        <style>{STYLES}</style>
      </defs>

      {}
      <g className="st-guava-leg">
        <path d={GUAVA_LEG_PATH} fill="none" stroke="#acacac" strokeWidth="1.5" />
        <Arrowhead x={GUAVA.x - ARROW_SIZE} y={GUAVA_CENTER_Y} fill="#acacac" />
      </g>
      <g className="st-cut" stroke="#eb4d4d" strokeWidth="2" strokeLinecap="round">
        <line x1={JUNCTION_X - 6} y1={(GUAVA_LEG_Y + GUAVA_CENTER_Y) / 2 - 6} x2={JUNCTION_X + 6} y2={(GUAVA_LEG_Y + GUAVA_CENTER_Y) / 2 + 6} />
        <line x1={JUNCTION_X + 6} y1={(GUAVA_LEG_Y + GUAVA_CENTER_Y) / 2 - 6} x2={JUNCTION_X - 6} y2={(GUAVA_LEG_Y + GUAVA_CENTER_Y) / 2 + 6} />
      </g>

      {}
      <path className="st-destination-leg" d={DESTINATION_LEG_PATH} pathLength={1} fill="none" stroke="#5eb92d" strokeWidth="1.5" />
      <g className="st-destination-arrow">
        <Arrowhead x={DESTINATION.x - ARROW_SIZE} y={DESTINATION_CENTER_Y} fill="#5eb92d" />
      </g>

      <Actor box={ITSP} label="ITSP" />
      <Actor box={GUAVA} label="Guava" accent />
      <Actor box={DESTINATION} label="Transfer Destination" />

      {}
      <g className="st-refer">
        <rect x={-REFER_WIDTH / 2} y={-REFER_HEIGHT / 2} width={REFER_WIDTH} height={REFER_HEIGHT} rx={REFER_HEIGHT / 2} fill="#1a2a3a" stroke="rgba(26,147,254,0.6)" strokeWidth="1" />
        <text y={3.5} textAnchor="middle" fill="#d7ecff" fontSize="9" fontFamily="ui-monospace, monospace" fontWeight="700">REFER</text>
      </g>

      <Caption animationClass="st-caption-1" lines={["The ITSP forwards the call to Guava."]} />
      <Caption animationClass="st-caption-2" lines={["Guava sends a REFER to the ITSP.", "Refer-To: sip:support@pbx.example.com"]} />
      <Caption animationClass="st-caption-3" lines={["The call to Guava ends."]} />
      <Caption animationClass="st-caption-4" lines={["The ITSP dials the transfer destination."]} />
    </svg>;
};

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.

<SipTransferAnimation />

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.

```python theme={null}
call.transfer("sip:support@pbx.example.com", "Let the caller know you're transferring them to support.")
```

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.

```python theme={null}
# Sends REFER with Refer-To: tel:+18005550199
call.transfer("+18005550199")
```

<Info>
  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.
</Info>

## Choosing a destination

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

```python theme={null}
# 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.
</Tip>

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

```python theme={null}
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.

```python theme={null}
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.
</Note>

## User-to-User (UUI) information

Many contact center platforms and PBXs expect call data in the standard `User-to-User` header ([RFC 7433](https://datatracker.ietf.org/doc/html/rfc7433)) 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.

```python theme={null}
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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.