How a trunk works
A trunk belongs to a project and has a direction:
On a call, the trunk is a participant named
server:sip:<trunkId>. Its
connection reports transport sip. The people on WhatsApp always hear your
PBX, and your PBX always hears them. It never hears its own audio. Whether your
PBX and your other participants hear each other follows the sessionβs
conference mode: on (the default),
they hear each other; off, each hears only the people on WhatsApp. SIP trunks
carry audio only.
Manage trunks with the Platform API. Reading requires sessions:read; every
change requires sessions:manage. Use a team key with projectId, or a project
token for its own project. Client tokens cannot manage trunks.
The TypeScript SDK adds Client.sipTrunks in its next release; until then, call
the API directly. You can also manage trunks in the Console: open a project, go to Calls, and
choose Manage SIP trunks. See Calls in the Console.
Find the SIP address
Every trunk in every project uses the same SIP address. Read it withGET /platform/sip/endpoint. It requires sessions:read and accepts a team key
or a project token:
When
status is sip_not_hosted, the environment has no SIP address for a
PBX to connect to, and the response returns no host or ports.
Configure your firewall with these values:
- Allow SIP on the listed ports, in both directions, between your PBX and the
addresses
hostresolves to. - Allow UDP in both directions between your PBXβs media ports and the
rtprange on the same addresses. - Prefer
tls: it encrypts signaling, and its SRTP requirement encrypts audio.udpandtcpcarry both unencrypted.
Client.sipTrunks.endpoint() and the CLI adds
polymorfa sip-trunk endpoint in their next releases.
Create a trunk
201 with the trunk and, for inbound and both
trunks, inboundCredentials:
maxConcurrentCalls calls at once (1 to 500, default 30).
Send WhatsApp calls to your PBX
Set these fields inoutbound:
Then route the sessionβs incoming calls to the trunk:
outbound
or both. Settings you omit keep their values; a later update without
sipTrunkId keeps the trunk. To stop sending calls to the trunk, send
"inboundRoute": "clients", which also clears sipTrunkId. On a Cloud API
Number, incoming calls reach the trunk only when hostCloudApiCalls is true;
see Cloud API Numbers. See
Update call settings.
When a routed call rings, Polymorfa calls targetUri. The call still rings for
your SDK participants, and they can answer it first. The request to your PBX
carries the WhatsApp call ID in the X-Polymorfa-Call-ID header.
- When your PBX answers, Polymorfa answers the WhatsApp call as
server:sip:<trunkId>. WithsipClaim: truethe answer claims the call: other participants stop ringing and cannot join. WithsipClaim: falsethe call stays shared and other participants can join. See Answer, join, or decline. - When your PBX does not answer, the WhatsApp call keeps ringing for your other participants until it is answered, declined, or the ring window ends.
- When your PBX hangs up, its connection leaves. If the trunk claimed the call, the call ends. When the WhatsApp call ends, Polymorfa hangs up your PBX.
call.connection_left event reports why its connection left:
A trunk that never answered reports only the departure, with no preceding join.
Call WhatsApp from your PBX
Configure your PBX with:- the
hostand a port from Find the SIP address; inboundCredentials.usernameandpasswordfor digest authentication; and- the WhatsApp number to call, in E.164 format, as the user part of the request
URI, for example
sip:+14155550100@SIP_HOST. The leading+is optional and a00prefix is accepted.
inbound.session to the session that places the calls. Polymorfa accepts a
call only when the digest credentials are valid, the request comes from one of
allowedAddresses, the trunk is enabled, its project is active, and the
number matches one of
allowedDestinations (or the list is empty). The call counts toward the same
per-minute outbound limit as calls placed through the API from that session.
The trunk claims every call it places. Your PBX receives:
Every authentication refusal is the same
403, so the response does not
reveal which check failed. Repeated failed authentication temporarily blocks
the sending address. When your PBX cancels a ringing call, the WhatsApp call
ends. When your PBX hangs up a connected call, the call ends for everyone.
Media and security
- Codecs. PCMU and PCMA (8 kHz) and Opus (48 kHz, mono), in the order of
codecs. For Opus, advertiseopus/48000/2in SDP; the audio remains mono. If an Opus mapping is unusable, an offered PCMU or PCMA codec can carry the call. Your PBX must offer media in the initial INVITE; calls without an offer are refused with488. Answers to calls from Polymorfa must retain an offered codecβs RTP payload number. Mid-call media changes are refused. - Packets. 20 ms of audio per RTP packet.
- Encryption. With
tls, signaling is encrypted and media uses SRTP with SDES keys (AES_CM_128_HMAC_SHA1_80orAES_CM_128_HMAC_SHA1_32). Plain RTP offers over TLS and SRTP offers over UDP or TCP are refused. Usetlswhen your PBX is reachable over the internet. - Addresses. Send media from the address in your PBXβs session description or from the address your signaling comes from. Polymorfa sends media back to the address your first media packet arrives from.
- PBX behind NAT. A private address (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10, orfc00::/7) in the session description works only when your signaling comes from a public address. Polymorfa then sends no media until your PBXβs first media packet arrives from that signaling address, so your PBX must start sending media. - Refused addresses. Session descriptions with a loopback, link-local,
multicast, unspecified, or reserved media address are refused: your PBXβs
call gets
488, and a call to your PBX ends withsip_unavailable. Trunk targets on private, loopback, or link-local addresses are rejected.
Manage trunks
PATCHchanges only the fields you send. OmitauthPasswordto keep the stored password; sendauthUsername: nullto remove both. SendexpectedRevisionto refuse the change with409 sip_trunk_revision_conflictwhen someone else changed the trunk first.- Changing
directiontooutboundremoves the inbound credentials. Changing anoutboundtrunk toinboundorbothcreates a new username; rotate its credentials to get the password. - Rotating credentials replaces the password. The old password stops working
within about a minute. Rotation returns
409 state_conflictforoutboundtrunks. - Deleting a trunk that a session still routes to returns
409 sip_trunk_in_use. Route the session back toclientsfirst. - Changes reach call routing within about a minute. Calls in progress continue.
- When a trunkβs project is deactivated, changing the trunk or rotating its
credentials returns
404 resource_not_found. Polymorfa sends no calls to the trunk and refuses its PBXβs calls within about a minute. You can still list, read, and delete the trunk.
Errors
See Errors for the full error format.