Dialplans & Routing
In FreeSWITCH, Dialplans are essentially the routing layer of your telephony engine. If you are familiar with web development, you can think of the dialplan as the routes/ directory in an Express.js application.
When a call arrives, FreeSWITCH parses the XML dialplan to match the incoming number (and other conditions) against a list of regular expressions. Once a match is found, it executes a sequence of actions (applications).
All routing logic in rdv.ai-freeswitch is located within the conf/dialplan/ directory.
Contexts: Public vs. Default
FreeSWITCH separates routing into “contexts” to ensure security and logical separation of traffic.
1. The Public Context (conf/dialplan/public/)
This is the most critical context for Rendez-vous.ai. Any call originating from the outside world (e.g., a customer calling from their cell phone via Twilio or VoIP.ms) enters the public context.
00_inbound_did.xml: This file handles the primary incoming routing. “DID” stands for Direct Inward Dialing (the phone number the user dialed).- The Handoff: In a traditional PBX, the dialplan might instruct FreeSWITCH to ring a specific desk phone. In our architecture, the dialplan matches the incoming DID and immediately executes a webhook/socket call to the Core API (
rdv.ai-api).
2. The Default Context (conf/dialplan/default/)
This context handles internal routing. It is primarily used for:
- Routing calls between internal SIP extensions (e.g.,
1000calling1001). - Local testing using softphones (Zoiper, Linphone).
- Outbound dialing rules (if the AI agent needs to initiate a call to the outside world).
How a Call is Handled (The XML Flow)
While the Core API controls the logic of the conversation, the dialplan dictates how the call physically starts and ends.
A simplified version of our inbound routing logic follows this sequence:
- Condition Matching: The dialplan checks the
destination_number(the number the caller dialed).
<condition field="destination_number" expression="^(.*)$">- Setting Variables: It exports essential variables that the Core API will need, such as the caller’s ID and the unique UUID of the FreeSWITCH channel.
<action application="set" data="api_url=http://${API_HOST}/api/telephony/incoming-call"/>- Triggering the API: Using a module like
mod_curlormod_httapi, FreeSWITCH sends an HTTP request to the Core API, passing along the call metadata. - Executing API Instructions: The API responds with dynamic XML instructions (or connects via Event Socket), telling FreeSWITCH to
answerthe call and open a WebSocket (uuid_audioormod_audio_fork) to stream the raw RTP audio back to the Node.js server.
Dynamic Variables (API_HOST)
To keep our Docker containers environment-agnostic, we heavily utilize dynamic variables within the XML dialplans.
When the rdv.ai-freeswitch container boots up, it reads environment variables (like API_HOST) and injects them into conf/vars.xml. This allows the exact same dialplan files to route webhooks to ngrok during local development and to internal Kubernetes services during production without needing code changes.