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

Developer cookbook

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 URISIP endpoint
Looks likesip:c-<token>@sip.thunderphone.comsip:agent-<id>-<letters>@sip.thunderphone.com
Created withPOST /v1/calls/register-sip, once per callPOST /v1/sip-endpoints, once
AuthenticationThe one-time token in the addressSIP digest username + password (and optionally a source-IP allowlist)
Good forApps that already call an API before transferring a call; per-call variablesPBXs and carriers that should just "point a trunk" at ThunderPhone
Valid for5 minutes, one callUntil 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_id is 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",
  "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:

SettingValue
SIP URIthe endpoint's sip_uri
Username / passwordusername / password (digest authentication; ThunderPhone challenges each call)
TransportTCP on port 5060 or TLS on port 5061 (recommended), or UDP on port 5060
CodecsG.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-Route header (sip-1.thunderphone.com or sip-2.thunderphone.com, or its IP address). If you restrict outbound SIP, allow those hosts as well as sip.thunderphone.com.
  • Audio goes elsewhere. sip.thunderphone.com carries 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.

What callers and your system see

  • Caller ID: the user part of the From header 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 as anonymous.
  • 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). 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: add referUrl="https://YOUR_SERVER/sip-transfer" to the <Dial>, and have that webhook read ReferTransferTarget and 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 at the agent's normal per-minute rate. There is no phone-number charge, because no ThunderPhone number carries the call. Your own carrier bills its side of the call as usual. See Pricing.

Test it

  1. Create a per-call URI or an endpoint as above.
  2. Dial it from your system, or from any SIP softphone (for an endpoint, enter the username and password).
  3. 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.