---
title: "Connect from Twilio"
description: "Send Twilio calls to a ThunderPhone agent over SIP with the CallSid, and take them back through your referUrl with the hand-off in the transfer address."
---

Your TwiML dials the agent's SIP address with the call's `CallSid` in a header.
When the caller needs a person, the agent sends a SIP `REFER`, Twilio passes the
transfer address with the hand-off inside it to your `referUrl` handler, and the
TwiML your handler returns decides where the caller goes, such as a Flex queue.

## Before you start

- A ThunderPhone agent and an organization [API key](/guides/api-keys).
- Twilio Programmable Voice, and somewhere to serve TwiML and a `referUrl`
  handler: your own server, or a TwiML Bin and a Twilio Function. Flex is
  optional.

## 1. Create the SIP endpoint

Twilio's `<Sip>` takes a username and password, so use a standard endpoint:

```bash
curl -X POST https://api.thunderphone.com/v1/sip-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": 12, "name": "Twilio"}'
```

Store the `password` from the response straight away; it is shown once. To
accept calls only from Twilio as well, see
[Restrict source IPs](/guides/sip-addresses#restrict-source-ips-optional).

## 2. Send calls with context

Return this TwiML for the call you want to hand to the agent:

```xml
<Response>
  <Dial answerOnBridge="true" referUrl="https://YOUR_SERVER/thunderphone-refer">
    <Sip username="tp409667538f83" password="YOUR_ENDPOINT_PASSWORD">sip:agent-12-jhxmginwjx@sip.thunderphone.com;transport=tls?X-Call-ID={{CallSid}}</Sip>
  </Dial>
</Response>
```

- `;transport=tls` matters: Twilio uses UDP unless told otherwise.
- `X-Call-ID` carries the `CallSid`. Twilio doesn't document a call-id header of
  its own, so add this one: ThunderPhone stores it as the call's
  `external_call_id`. `{{CallSid}}` works in a TwiML Bin; from your own server,
  write the `CallSid` of the incoming request.
- Add more context the same way, such as `&amp;X-Account-Id=12345`. Inside
  TwiML, write `&` as `&amp;`, since a bare `&` is invalid XML. Each reaches the
  prompt as a variable, here `{{sip_x_account_id}}`. Twilio allows `x-` headers
  and `User-to-User`, percent-encoded, with the whole URI under 255 characters
  and the headers under 1,024.
- `referUrl` lets Twilio accept the agent's `REFER`; without it the transfer
  fails with [error 13802](https://www.twilio.com/docs/api/errors/13802).
  `answerOnBridge="true"` keeps the caller connected until the new destination
  answers, so a failed transfer can fall back.

Studio's **Connect Call To** widget doesn't document custom headers or
`referUrl`, so from Studio hand the call to this TwiML, for example with a
**TwiML Redirect** widget.

## 3. Get the call back

The agent transfers with a `REFER` whose transfer address carries the hand-off
id and intent. Add this transfer number (see
[Add the transfer number](/guides/contact-centers/overview#add-the-transfer-number)
and [Headers inside the transfer address](/guides/call-transfers#headers-inside-the-transfer-address)):

```json
{
  "name": "Twilio support",
  "number": "sip:support@acme.sip.twilio.com",
  "headers": {"X-Handoff-Id": "{{handoff_id}}", "X-Handoff-Intent": "{{handoff_intent}}"},
  "headers_in_uri": true
}
```

Twilio sends the whole address to your `referUrl` as `ReferTransferTarget`, for
example `<sip:support@acme.sip.twilio.com?X-Handoff-Id=Zr3mY0b1Q9xWw2kPq4s7Tg&X-Handoff-Intent=billing_dispute>`.
Your handler reads the headers out of it and returns TwiML for where the caller
goes next. The address's user part (`support`) is yours to choose, so you can
add one transfer number per destination and route on it.

This Twilio Function enqueues the caller to Flex with the hand-off as task
attributes and looks the summary up by hand-off id. The lookup runs in a
Function because Studio's HTTP Request widget can't send the `Authorization`
header:

```js
exports.handler = async (context, event, callback) => {
  const target = (event.ReferTransferTarget || "").replace(/^<|>$/g, "");
  const params = new URLSearchParams(target.split("?")[1] || "");
  const handoffId = params.get("X-Handoff-Id") || "";
  const attributes = { handoff_id: handoffId, intent: params.get("X-Handoff-Intent") || "" };

  if (handoffId) {
    const response = await fetch(
      "https://api.thunderphone.com/v1/calls/handoff?handoff_id=" + encodeURIComponent(handoffId),
      { headers: { Authorization: "Bearer " + context.THUNDERPHONE_API_KEY } }
    );
    if (response.ok) {
      const call = await response.json();
      attributes.summary = (call.handoff && call.handoff.summary) || "";
    }
  }

  const twiml = new Twilio.twiml.VoiceResponse();
  twiml.enqueue({ workflowSid: context.FLEX_WORKFLOW_SID }).task({}, JSON.stringify(attributes));
  callback(null, twiml);
};
```

Keep the API key in the Function's environment variables. See
[Look up a handed-off call](/api-reference/calls#look-up-a-handed-off-call)
for the other fields.

## Test it

Call a Twilio number that returns the TwiML above, ask the agent for a person,
and check that your `referUrl` handler receives `ReferTransferTarget` with both
headers and that the caller reaches the queue. Then open the call with
`GET /v1/calls/{call_id}`: `external_call_id` should be the `CallSid`.

## Limits

- Twilio limits `Refer-To` to 256 characters. ThunderPhone keeps the address to
  254 and leaves out a header that doesn't fit, so keep header names and values
  short.
- Twilio doesn't document reading headers on a `BYE`, so a return can't carry
  the hand-off back to Twilio. Use a `REFER` as above.
- Twilio doesn't accept a `REFER` on a call connected through `<Conference>` or
  `<Queue>`, so dial the agent with `<Dial><Sip>` as above.
