ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Contact centers

Connect from Twilio

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.
  • 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:

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.

2. Send calls with context

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

<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. 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 and Headers inside the transfer address):

{
  "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:

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