For some time I have been building the telephony layer of a conference-bridge platform, in collaboration with a small team that owns the REST API and the client applications. The API and the clients are not mine; the FreeSWITCH parts are.
What the Platform Does
The platform is a per-conversation conference bridge with a REST API in front of it. A user joins a conversation from a WebRTC client in the browser or a SIP softphone on their desktop, and lands in a FreeSWITCH conference room keyed by the conversation identifier. Calls are recorded per participant, the recordings are uploaded out-of-band, and the API is notified at every state change so that the chat and presence sides of the platform stay in sync.
The whole stack runs in Docker Compose across local, development,
staging, and production environments, with shared volumes for the SSL
certificates and the recordings. The development environment is deployed
to a Digital Ocean droplet using the same compose file as the local
setup, with environment-specific overrides loaded from a .env file.
The Telephony Layer
FreeSWITCH runs in its own container. I configured two SIP profiles: an internal one for trusted endpoints (registered softphones, internal services), and an external one for untrusted public endpoints. Both profiles handle WebRTC over WebSocket and Secure WebSocket, so the browser client and the SIP softphone end up in the same conference without translation.
SIP authentication is dynamic. Instead of a static directory.xml with
the user list, FreeSWITCH calls the platform’s user service over HTTP
through mod_xml_curl and gets back an XML directory entry for the
caller. New users do not require a FreeSWITCH config reload.
The Dialplan Calls Out to the API
The destination number on every call is the conversation identifier.
The dialplan extracts it, sets conference_id, and runs three things in
order:
- exports an
api_hangup_hookthat posts to a call-terminate endpoint on the API with the call UUID, - curls the API call-join endpoint with a JSON payload of
callId,userIdandconversationId, - bridges the caller into the FreeSWITCH conference named after the identifier.
The dialplan stays small. The API is authoritative for the conversation state: who is in the room, when they joined, when they left, and what the recording for their leg should be tagged with.
The Recording Pipeline
Recording is per-leg, follows transfers, and runs through a post-process exec hook. When a call leg ends, FreeSWITCH calls a small recording service over HTTP, passing the username, the call UUID, the call start time and the conversation identifier. The recording service reads the file from the shared volume that FreeSWITCH writes to, uploads it to object storage with the metadata, and tells the API where the file landed.
Keeping the upload out of the FreeSWITCH container matters because the upload can be slow, and a slow upload should not hold a FreeSWITCH worker thread.
SIP Monitoring
FS_HEPLIFY_URL points FreeSWITCH at a HEPLIFY-Server endpoint over
UDP with HEP version 3, and the captured SIP traffic shows up in a
Homer dashboard. When a SIP softphone reports that a call did not
connect, the trace in Homer shows which side hung up and why, without
having to turn on FreeSWITCH SIP tracing.
Tooling
Two small tools live alongside the FreeSWITCH service. An ESL client for poking at the FreeSWITCH event socket from the host. A test softphone container that registers as a regular user and can place or receive a call against the running platform, useful for end-to-end smoke checks when a WebRTC client is not at hand.
The platform runs in production. Further work on the API and client side belongs to the rest of the team.