---
title: "Connect from Amazon Connect"
description: "Send Amazon Connect calls to a ThunderPhone agent over SIP with the contact id and UUI, and resume your flow with the agent's hand-off."
---

Your Amazon Connect flow sends the caller to the agent with the contact id and
any UUI you set. When the agent is done, it hangs up, your flow carries on, and
a Lambda fetches the hand-off by contact id.

## Before you start

- A ThunderPhone agent and an organization [API key](/guides/api-keys).
- Amazon Connect's **External voice transfer** connector. Request the
  "External voice transfer connectors per account" quota increase first: the
  menu stays hidden until AWS approves it. AWS bills each connector per day
  ([$100 a day since July 21, 2025](https://aws.amazon.com/about-aws/whats-new/2025/07/amazon-connect-announces-per-day/))
  plus per-minute usage.

<Warning>
  AWS's current admin guide no longer has pages for external voice transfer.
  Before you build on it, confirm with AWS that the connector is available for
  your instance and Region.
</Warning>

## 1. Create the SIP endpoint

The connector doesn't document SIP digest authentication, so create an
endpoint that accepts calls by source address:

```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": "Amazon Connect",
    "auth": "ip_allowlist",
    "allowed_source_cidrs": ["CONNECT_SIGNALLING_RANGE_1", "CONNECT_SIGNALLING_RANGE_2"]
  }'
```

Get the connector's SIP signalling ranges for your Region from AWS. Each must be
a public IPv4 range of `/16` or narrower (IPv6 `/48` or narrower). See
[Platforms that authenticate by source IP](/guides/sip-addresses#platforms-that-authenticate-by-source-ip)
for the rules and the risk of an address shared by every customer of the
platform. Keep the response's `sip_uri`, for example
`sip:agent-12-jhxmginwjx@sip.thunderphone.com`.

## 2. Send calls with context

1. In the Amazon Connect console, open **External Voice Systems → Voice
   Transfer Integrations** and choose **Create**.
2. Pick the destination type (an SBC) and add an outbound route with host
   `sip.thunderphone.com`, port `5061` and protocol TLS. The endpoint needs
   TLS or TCP (port `5060`) and refuses UDP. AWS doesn't document which
   protocols the connector offers, so confirm with AWS that it can use one of
   them.
3. In your flow, add a **Transfer to phone number** block with **Transfer to**
   set to **External voice system**, and set:
   - **Destination ARN:** the connector.
   - **To User:** the user part of the endpoint's `sip_uri`, such as
     `agent-12-jhxmginwjx`.
   - **UUI** (optional): context for the agent. Connect sends it hex-encoded.
   - **Resume flow after disconnect:** Yes, so the flow continues when the
     agent hangs up.

Connect sends the contact id in `X-Amzn-ConnectContactId`. ThunderPhone stores
it as the call's `external_call_id`, which is how your flow finds the call
later. The UUI reaches the prompt as `{{sip_uui}}`. Third-party integrations
document the contact-id header; AWS's own docs don't, so check it on your first
test call.

## 3. Get the call back

AWS documents no way for Connect to read headers on the `BYE` or to accept a
`REFER` from the agent. So the agent returns the call: it says goodbye and
hangs up, and your flow resumes after the transfer block. Add this transfer
number (see [Add the transfer number](/guides/contact-centers/overview#add-the-transfer-number)):

```json
{"name": "Amazon Connect", "action": "return"}
```

Then fetch the hand-off with an **Invoke AWS Lambda function** block after the
transfer. The Lambda looks the call up by contact id:

```python
import json
import os
import urllib.error
import urllib.parse
import urllib.request


def lambda_handler(event, context):
    contact_id = event["Details"]["ContactData"]["ContactId"]
    url = "https://api.thunderphone.com/v1/calls/handoff?" + urllib.parse.urlencode(
        {"external_call_id": contact_id}
    )
    request = urllib.request.Request(
        url, headers={"Authorization": "Bearer " + os.environ["THUNDERPHONE_API_KEY"]}
    )
    try:
        with urllib.request.urlopen(request, timeout=5) as response:
            call = json.load(response)
    except (urllib.error.URLError, TimeoutError):
        return {"found": "false"}
    handoff = call.get("handoff") or {}
    return {
        "found": "true",
        "intent": handoff.get("intent") or "",
        "summary": handoff.get("summary") or "",
    }
```

The block returns a flat map, so the flow can branch on `intent` or set
`summary` as a contact attribute for the person who takes the call. Run the
block synchronously (up to 8 seconds) and store the API key encrypted, for
example in AWS Secrets Manager. The response fields are in
[Look up a handed-off call](/api-reference/calls#look-up-a-handed-off-call).

## Test it

Call a number on your Connect instance that reaches the transfer block, ask the
agent for a person, and check that the flow resumes. Then open the call with
`GET /v1/calls/{call_id}`: `sip_headers` should list
`x-amzn-connectcontactid`, and `external_call_id` should equal the `ContactId`
your Lambda receives. If the transfer fails with SIP `404`, ThunderPhone didn't
find the endpoint's user part: it must be in both the request URI and the `To`
header.

## Limits

- AWS documents no way to read headers on the `BYE`, including
  `X-TP-Handoff-Id`, so the contact id is the only link back to the call.
- AWS doesn't document a maximum UUI length for the connector.
- A synchronous Lambda block times out after 8 seconds.
