Open in
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
referUrlhandler: 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=tlsmatters: Twilio uses UDP unless told otherwise.X-Call-IDcarries theCallSid. Twilio doesn't document a call-id header of its own, so add this one: ThunderPhone stores it as the call'sexternal_call_id.{{CallSid}}works in a TwiML Bin; from your own server, write theCallSidof the incoming request.- Add more context the same way, such as
&X-Account-Id=12345. Inside TwiML, write&as&, since a bare&is invalid XML. Each reaches the prompt as a variable, here{{sip_x_account_id}}. Twilio allowsx-headers andUser-to-User, percent-encoded, with the whole URI under 255 characters and the headers under 1,024. referUrllets Twilio accept the agent'sREFER; 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-Toto 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 aREFERas above. - Twilio doesn't accept a
REFERon a call connected through<Conference>or<Queue>, so dial the agent with<Dial><Sip>as above.