Open in
Connect over SIP (no phone number)
Send calls to a ThunderPhone agent straight from your PBX, carrier or SIP application, with a one-time SIP URI per call or a permanent SIP address.
If your own system already handles phone numbers (a PBX, a contact-center platform, or a carrier account where you rotate numbers), you can send calls to a ThunderPhone agent over plain SIP. You don't import or buy a number on ThunderPhone. Your system dials a SIP address and the agent answers.
There are two kinds of SIP address:
| Per-call SIP URI | SIP endpoint | |
|---|---|---|
| Looks like | sip:c-<token>@sip.thunderphone.com | sip:agent-<id>-<letters>@sip.thunderphone.com |
| Created with | POST /v1/calls/register-sip, once per call | POST /v1/sip-endpoints, once |
| Authentication | The one-time token in the address | SIP digest username + password (and optionally a source-IP allowlist), a source-IP allowlist alone for platforms that authenticate by IP, or a secret address for providers that can only forward to an address |
| Good for | Apps that already call an API before transferring a call; per-call variables | PBXs and carriers that should just "point a trunk" at ThunderPhone |
| Valid for | 5 minutes, one call | Until you delete it |
Both kinds route to one agent in your organization, and both show up in call history like any other inbound call.
Per-call SIP URI
1. Register the call
curl -X POST https://api.thunderphone.com/v1/calls/register-sip \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": 12,
"to_number": "+14155550142",
"variables": {"customer_name": "Ada"}
}'{
"call_id": 862197007562,
"sip_uri": "sip:c-1790372992.Qm3xT8vLk2PzR7nYw4HbJa.uF6sD1gK9eW2cX5vB8nM0q@sip.thunderphone.com",
"expires_at": "2026-09-25T21:49:52Z",
"agent_id": 12,
"to_number": "+14155550142"
}to_number(optional) is the number the caller dialed on your side. It is stored as the call's destination in call history, and the agent sees it as its own number.variables(optional) fill{{placeholders}}in the agent's prompt, the same way as for outbound calls.call_idis the id the call will have in call history (GET /v1/calls/{call_id}) once it connects.
2. Dial the URI
Send the call to sip_uri within 5 minutes. The URI works for one call only:
once a call has used it, it is spent, even if that call ends quickly.
With Twilio, for example, return this TwiML for the call you want to hand over:
<Response>
<Dial>
<Sip>sip:c-1790372992.Qm3xT8vLk2PzR7nYw4HbJa.uF6sD1gK9eW2cX5vB8nM0q@sip.thunderphone.com;transport=tcp</Sip>
</Dial>
</Response>From a PBX, dial the URI as a SIP destination. No username or password is needed; the token is the credential, so treat the URI like a password until it has been used.
SIP endpoint (permanent address)
1. Create the 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": "Main PBX"}'{
"id": 7,
"name": "Main PBX",
"agent_id": 12,
"sip_uri": "sip:agent-12-jhxmginwjx@sip.thunderphone.com",
"auth": "digest",
"username": "tp409667538f83",
"password": "8mJ0…",
"allowed_source_cidrs": [],
"status": "active",
"created_at": "2026-09-25T21:46:18Z"
}2. Point your system at it
Configure a SIP trunk or destination with:
| Setting | Value |
|---|---|
| SIP URI | the endpoint's sip_uri |
| Username / password | username / password (digest authentication; ThunderPhone challenges each call). None for an ip_allowlist endpoint (below) |
| Transport | TCP on port 5060 or TLS on port 5061 (recommended), or UDP on port 5060 |
| Codecs | G.711 (PCMU/PCMA) or G.722 |
These transport and codec settings apply to per-call URIs too. Two network details matter for both kinds of address:
- Follow Record-Route. After the first INVITE, the rest of the call's
signalling (ACK, hang-up, transfers) goes to the specific ThunderPhone server
that answered, which is named in the
Record-Routeheader (sip-1.thunderphone.comorsip-2.thunderphone.com, or its IP address). If you restrict outbound SIP, allow those hosts as well assip.thunderphone.com:172.170.39.202(sip),13.86.104.121(sip-1) and20.80.98.90(sip-2). - Audio goes elsewhere.
sip.thunderphone.comcarries signalling only. Allow media (RTP) to the addresses and ports in the SDP answer.
Every call to the address reaches the endpoint's agent, whatever number your caller dialed on your side. You never import numbers, so rotating them on your side needs no change on ThunderPhone.
Pass the dialed number (optional)
To record which of your numbers the caller dialed, send it on the INVITE in an
X-Original-To header (for example X-Original-To: +14155550142) or in a
standard Diversion header. ThunderPhone stores it as the call's destination.
The destination is chosen in this order: the to_number given when a per-call
URI was registered, then a number in X-Original-To, then one in Diversion.
If none of these gives a number, it is shown as sip:agent-12-jhxmginwjx (the
endpoint's address) for an endpoint, or sip:call for a per-call URI.
With Twilio you can add the header to the SIP URI:
sip:agent-12-jhxmginwjx@sip.thunderphone.com;transport=tcp?X-Original-To=%2B14155550142.
Restrict source IPs (optional)
Set allowed_source_cidrs to only accept calls from your own signalling
addresses, on top of the username and password:
curl -X PATCH https://api.thunderphone.com/v1/sip-endpoints/7 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_source_cidrs": ["203.0.113.0/28"]}'Calls from any other address are refused before they reach the agent. Use the addresses your system sends SIP signalling from (for a carrier such as Twilio, its published signalling IP ranges), not your media addresses.
Platforms that authenticate by source IP
Some contact-center platforms, such as Talkdesk, Amazon Connect and Amazon
Chime Voice Connector, identify themselves only by the addresses
they send SIP from and document no password authentication. For these, create
the endpoint with auth set to ip_allowlist and list the platform's SIP
signalling ranges:
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 Chime",
"auth": "ip_allowlist",
"allowed_source_cidrs": ["3.80.16.0/23", "99.77.253.0/24"]
}'The response has "username": null and no password. Point the platform at
the sip_uri over TLS (port 5061) or TCP (port 5060): an ip_allowlist
endpoint refuses every call over UDP, because a UDP source address can be
forged.
The list must have at least one entry. Each must be a public IPv4 range of
/16 or narrower or an IPv6 range of /48 or narrower. Use the signalling
ranges the platform publishes for the regions you use, not its media ranges
and not a whole cloud provider's.
To switch an existing endpoint, PATCH it with auth (and
allowed_source_cidrs if its list doesn't meet these rules). Switching back
to digest returns a new password once.
Providers that can only forward to an address
Some providers' call forwarding takes a SIP address and nothing else: no
username, no password, no fixed source addresses. For these, create the
endpoint with auth set to secret_address:
{
"agent_id": 12,
"name": "Sonetel forwarding",
"auth": "secret_address"
}Its address ends in 28 random letters, and that address is the credential.
The response returns it in full once, in secret_sip_uri; everywhere else
sip_uri shows it masked. Paste it into the provider's forwarding box. Calls
over UDP, TCP or TLS are accepted.
Platforms that dial a phone number
Five9, NICE CXone and Zoom Contact Center send a call to a phone number and
can't dial the endpoint's agent-… address. Give an ip_allowlist endpoint
up to 5 dial_in_numbers: the platform then sends its INVITE to
sip:<number>@sip.thunderphone.com and reaches the endpoint's agent. Zoom
Contact Center also needs TLS with SRTP, so set srtp to required (SRTP,
like dial-in numbers, is available on ip_allowlist endpoints only):
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": "Zoom Contact Center",
"auth": "ip_allowlist",
"allowed_source_cidrs": ["203.0.113.0/24", "198.51.100.0/24"],
"dial_in_numbers": ["999123456789012"],
"srtp": "required"
}'Replace the example ranges (documentation addresses, which the API refuses)
with your platform's SIP signalling ranges. Enter each number exactly as the
platform dials it, with or without the +
(Zoom's 15-digit 999… numbers have none); spaces, dashes and parentheses are
removed. A number counts as an address, not a password: calls to it are
checked against allowed_source_cidrs and must arrive over TCP or TLS, the
same as calls to the sip_uri. A number can't already be in use on
ThunderPhone, as a phone number or on another SIP endpoint; creating or
updating with one returns 409. To change the list, PATCH the endpoint with
the full new dial_in_numbers.
Calls to a dial-in number are SIP-address calls: no ThunderPhone number
carries them, so they don't appear under phone numbers and have no
phone-number charge. The call's destination is the number in X-Original-To
or Diversion when the platform sends one, otherwise the dial-in number.
Pass call context in SIP headers
A contact-center platform or PBX usually knows things about the call before it reaches the agent: its own call or contact id, what the caller chose in the IVR, the account number they entered. Send them as SIP headers on the INVITE and the agent can use them.
ThunderPhone reads two kinds of header:
- every header whose name starts with
X-(for exampleX-Amzn-ConnectContactIdorX-Intent), exceptX-TP-*andX-LK-*, which ThunderPhone's own SIP servers use and are always dropped; User-to-User(UUI). A value marked;encoding=hexis decoded to text when it decodes to UTF-8; the value as sent is kept as well.
This works on both kinds of SIP address, and on numbers imported through a manual SIP connection. It does not apply to ThunderPhone numbers or to numbers on a Twilio or Telnyx connection.
Use them in the prompt
Each header becomes a call variable named sip_
plus the header name in lower case, with every run of characters other than
letters and digits turned into one _:
| Header | Variable |
|---|---|
X-Amzn-ConnectContactId | {{sip_x_amzn_connectcontactid}} |
X-Intent | {{sip_x_intent}} |
User-to-User | {{sip_uui}} (decoded) and {{sip_uui_raw}} (as sent, only when it was hex-encoded) |
A header only reaches the agent where its prompt names it; nothing is added
to the conversation otherwise. A variable of the same name from
register-sip or from your call setup webhook
wins over the header. Give each placeholder a default
({{sip_x_intent|unknown}}) so calls without the header still read well.
Limits
At most 32 headers per call, 1,024 characters per value and 8 KB in total; headers past these limits are dropped. Control characters are removed.
Where they show up
- Call history:
GET /v1/calls/{call_id}returnssip_headers(header name in lower case → value), ornullfor a call without any. - Your platform's call id: the first of these headers the call carried is
stored as the call's
external_call_id:X-Amzn-ConnectContactId(Amazon Connect),x-inin-cnv(Genesys Cloud, with the trunk's Conversation Headers setting on),X-Talkdesk-Interaction-Id(Talkdesk),X-ZOOM-Call-Sid(Zoom Contact Center),X-CallID(Five9),X-Call-ID(set it yourself on any other platform) andX-Genesys-Conversation-Id. When the agent hands the call back to your platform,GET /v1/calls/handofffinds the call by it. - Webhooks: the
telephony.incomingevent includessip_headerswhen the call carried some. For numbers on a manual SIP connection, so does the blocking call configuration request and the agent's call setup webhook.
What callers and your system see
- Caller ID: the user part of the
Fromheader is stored as the caller. A phone number (digits, optionally with+, spaces, dashes, dots or brackets) is normalized to E.164; anything else, such as a SIP username, is kept as sent. At most 32 characters are kept, and a missing caller is stored asanonymous. - Refused calls: an unknown or expired per-call URI gets SIP
404 Not Found. An already-used per-call URI, a disabled endpoint, or a source outside the allowlist (including any UDP call to an endpoint with an allowlist) is disconnected before the agent starts: your system may see the call fail, or connect and then end straight away. These refusals do not create a call-history entry. If your organization is out of credit or has reached its spending limit, the call is refused and shows in call history as a failed call with no charge. To retry a per-call attempt, register a new URI. - Transfers: the agent's cold transfers work (ThunderPhone sends a SIP
REFER, which your system must accept). A transfer number can also be a SIP address in your system, such as a queue, with SIP headers for it to read; see Transfer to a SIP address. The agent can also hand the call back by hanging up with routing headers on theBYE; see Return the call to your contact center. Warm transfers are not available on SIP-address calls, because there is no ThunderPhone number to show as caller ID for the second leg. With Twilio, the TwiML above connects the agent but does not handle transfers: addreferUrl="https://YOUR_SERVER/sip-transfer"to the<Dial>, and have that webhook readReferTransferTargetand return TwiML that dials the transfer destination (see Twilio's SIP REFER guide). - Text messages: the SIP address and the dialed number can't send texts. The agent can text during a SIP-address call only if it has a separate SMS-capable number set up for texting; otherwise its SMS tool is unavailable.
- Registration: ThunderPhone does not accept SIP
REGISTER. Send calls to the address; don't register to it.
Pricing
SIP-address calls are billed like any other call. There is no phone-line part, because no ThunderPhone number carries the call. Your own carrier bills its side of the call as usual. See Pricing.
Test it
- Create a per-call URI or an endpoint as above.
- Dial it from your system, or from any SIP softphone (for a
digestendpoint, enter the username and password; anip_allowlistendpoint only answers calls from its allowed ranges). - After hanging up, open the call in Calls in the dashboard, or fetch it
with
GET /v1/calls/{call_id}. It shows the caller, the destination (see the order under "Pass the dialed number") and the transcript.
API details: SIP addresses API reference.