Skip to main content
A SIP trunk connects a phone system you already run, such as a PBX or contact center platform, to the WhatsApp calls of a connected Number. Incoming WhatsApp calls can ring on your PBX, and your PBX can call WhatsApp users through the Number. Polymorfa does not host a PBX and does not provide phone numbers. SIP trunks are part of Calls. Every team that uses Calls can create trunks in any of its projects; no enrollment is needed. In the Console, team owners and admins add, change, and delete trunks. To get the SIP address and ports your PBX connects to, email support@polymorfa.com.

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.

Create a trunk

The response returns 201 with the trunk and, for inbound and both trunks, inboundCredentials:
The inbound password appears only in this response and in a credential rotation response. Store it in your PBX right away. Responses never return passwords again; outbound.hasPassword shows whether one is stored.
A project holds up to 100 trunks. Each trunk carries at most maxConcurrentCalls calls at once (1 to 500, default 30).

Send WhatsApp calls to your PBX

Set these fields in outbound: Then route the session’s incoming calls to the trunk:
The trunk must belong to the session’s project and have direction 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>. With sipClaim: true the answer claims the call: other participants stop ringing and cannot join. With sipClaim: false the 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.
The trunk’s 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 SIP address and ports Polymorfa support gave you;
  • inboundCredentials.username and password for 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_ADDRESS. The leading + is optional and a 00 prefix is accepted.
Set 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. Your PBX must offer media in the initial INVITE; calls without an offer are refused with 488. 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_80 or AES_CM_128_HMAC_SHA1_32). Plain RTP offers over TLS and SRTP offers over UDP or TCP are refused. Use tls when 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, or fc00::/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 with sip_unavailable. Trunk targets on private, loopback, or link-local addresses are rejected.

Manage trunks

  • PATCH changes only the fields you send. Omit authPassword to keep the stored password; send authUsername: null to remove both. Send expectedRevision to refuse the change with 409 sip_trunk_revision_conflict when someone else changed the trunk first.
  • Changing direction to outbound removes the inbound credentials. Changing an outbound trunk to inbound or both creates 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_conflict for outbound trunks.
  • Deleting a trunk that a session still routes to returns 409 sip_trunk_in_use. Route the session back to clients first.
  • 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.