Porting your business phone number to add AI call handling is an operational risk most companies don’t need to take. Your number may be tied to other services. Your carrier contract may have obligations. And if something goes wrong mid-port, customer calls go nowhere.
SIP forwarding sidesteps all of it. Your carrier forwards calls from your existing PSTN number to a SIP endpoint. The AI agent picks up, handles the conversation, and transfers to your team when needed. Your number stays where it is. Your carrier contract is untouched. Your customers notice nothing except a better first interaction.
In this post, we’ll build an AI receptionist in Python using SignalWire’s Agents SDK that receives calls forwarded from a client’s PSTN number via SIP, identifies the caller’s intent, and transfers them to the right department without porting your number. Just Tony Stark’s J.A.R.V.I.S. behind the front desk, without touching a single carrier contract.
How It Works: PSTN Number → Carrier → SIP → SignalWire Voice AI Agent
The architecture is straightforward:
Customer dials the client's PSTN phone number
↓
Client's carrier receives the call
↓ Carrier forwards via SIP INVITE
SignalWire SIP Address (attached to a Script resource)
↓ fetches SWML
Your Voice AI Agent (served via ngrok or deployed)
↓ AI conversation happens
Optional: transfer back to client's team via SIP or PSTN
The key piece is the SIP address in SignalWire. You create a Script resource that points to your agent’s URL, then attach a SIP address to it. That SIP address is what you hand to the client’s carrier: “forward calls from your PSTN number to this SIP destination.”
When the carrier forwards a call and a SIP INVITE arrives at that address, SignalWire fetches SWML (SignalWire Markup Language) from your agent. The agent serves a JSON document describing the AI conversation — voice settings, prompts, tools — and SignalWire executes it. The caller hears the AI, the AI runs the conversation, and your tools fire when needed.
The agent code itself doesn’t care whether the call originated from a PSTN number forwarded via SIP or from a SignalWire-owned number. Both result in the same SWML fetch. The SIP address is just the bridge between the carrier and your AI.
Building the Voice AI Agent in Python
The agent uses the SignalWire Agents SDK. It is the same framework whether you’re handling inbound PSTN, SIP, or both. Install it:
pip install signalwire-agents
Here’s the receptionist AI agent. It greets callers, identifies their intent, logs the interaction, and transfers to the appropriate department:
from signalwire_agents import AgentBase
from signalwire_agents.core.function_result import SwaigFunctionResult
class ReceptionistAgent(AgentBase):
def __init__(self):
super().__init__(name="receptionist", route="/agent")
# Voice configuration
self.add_language("English", "en-US", "rime.spore")
self.add_hints(["receptionist", "transfer", "department"])
# Prompt
self.prompt_add_section(
"Role",
body="You are an AI receptionist for a business. "
"Calls arrive via SIP from the company's existing phone system."
)
self.prompt_add_section(
"Tasks",
body="Follow these steps:",
bullets=[
"Answer warmly and identify yourself as the AI receptionist",
"Ask the caller for their name",
"Ask what they need help with",
"Determine which department can help: sales, support, or billing",
"Confirm the department with the caller",
"Use the log_call tool to record the interaction",
"Use the transfer_to_department tool to connect them",
]
)
self.prompt_add_section(
"Rules",
body="Follow these guidelines:",
bullets=[
"Be concise and professional",
"If the caller's intent is unclear, ask a clarifying question",
"Do NOT transfer until you've confirmed the department",
"Keep responses short — this is a voice conversation",
]
)
self.set_post_prompt(
"Summarize: caller name, intent, department routed to, "
"and whether transfer succeeded."
)
# Tools
self.define_tool(
name="log_call",
description="Log the caller's information and intent before transferring",
handler=self._log_call,
parameters={
"type": "object",
"properties": {
"caller_name": {
"type": "string",
"description": "Caller's name"
},
"intent": {
"type": "string",
"description": "What the caller needs help with"
},
"department": {
"type": "string",
"description": "Department to route to: sales, support, or billing"
},
},
"required": ["caller_name", "intent", "department"]
}
)
self.define_tool(
name="transfer_to_department",
description="Transfer the caller to the appropriate department "
"after logging their info",
handler=self._transfer_to_department,
parameters={
"type": "object",
"properties": {
"department": {
"type": "string",
"description": "Department: sales, support, or billing"
},
},
"required": ["department"]
}
)
def _log_call(self, args, raw_data=None):
caller_name = args.get("caller_name", "")
intent = args.get("intent", "")
department = args.get("department", "")
# In production, persist to a CRM or database
print(f"[LOG] Caller: {caller_name}, Intent: {intent}, Department: {department}")
return SwaigFunctionResult(
f"Logged: {caller_name} needs {intent}, routing to {department}. "
"Now transfer the caller."
)
def _transfer_to_department(self, args, raw_data=None):
department = args.get("department", "support")
# SIP URIs pointing back to the PBX extension
destinations = {
"sales": "sip:sales@client-pbx.example.com",
"support": "sip:support@client-pbx.example.com",
"billing": "sip:billing@client-pbx.example.com",
}
destination = destinations.get(department, destinations["support"])
print(f"[TRANSFER] Routing to {department} at {destination}")
return (
SwaigFunctionResult(
f"Connecting you to our {department} team now.",
post_process=True
)
.connect(destination=destination)
)
agent = ReceptionistAgent()
if __name__ == "__main__":
agent.run()
A few things to notice:
- The prompt is written for voice. Instructions are action-oriented (“Ask the caller for their name”), tool references are explicit (“Use the
log_calltool”), and there’s a guardrail about brevity. Voice AI agents that produce long paragraphs sound unnatural. - The transfer uses phone numbers or SIP URIs. In this scenario, the PSTN number forwards to your AI via SIP, and after the AI handles the conversation, it transfers to the team — either to a direct phone number (
+15551234567) or, if the client has a PBX, to a SIP extension (sip:sales@client-pbx.example.com). Either way, the caller ends up with the right person.
post_process=True on the transfer result tells the AI to speak the response (“Connecting you to our sales team now”) before executing the transfer action. Without it, the transfer would happen silently.
Setting Up the SIP Address for the Carrier
With the agent code ready, you need to create the SIP address that the client’s carrier will forward PSTN calls to. In SignalWire’s dashboard:
1. Create a SIP Credential
This is for your softphone (or the client’s PBX) to authenticate with SignalWire.
- Go to Resources → + Add New → SIP
- Set a username and password
- Note the SIP domain (format:
your-space.sip.signalwire.com)
2. Create a Script Resource with a SIP Address
This connects your agent to a dialable SIP address.
- Go to Resources → + Add New → Script → New SWML Script
- Set Handle Calls Using to External URL
- Set the Primary Script URL to your agent’s URL with Basic Auth:
https://user:password@your-agent-url.ngrok-free.app/agent - Click Create
- On the resource page, go to Addresses & Phone Numbers → + Add → SIP Address
- Name it (e.g., receptionist) — this becomes your dialable SIP address
Now any call forwarded to receptionist@your-space.dapp.signalwire.com will hit your AI agent. This is the SIP address you provide to the carrier. Configure the PSTN number to forward calls to it. The carrier handles the PSTN-to-SIP conversion, and your agent handles the conversation.
In production, the carrier admin adds this SIP address as a forwarding destination. Some carriers let you do this through a dashboard; others require a support ticket. Either way, it’s a one-time configuration on the carrier side.
Transferring Calls Back to the Team
The AI handles the initial conversation, but eventually the caller needs a human. Since the call originated from your PSTN number (forwarded via their carrier), you have options for where to send it next.
The transfer tool uses .connect() with a SIP URI:
.connect(destination="sip:sales@client-pbx.example.com")
Or to another AI agent on the same server using swml_transfer(). This is useful if you have specialized agents per department.
The round-trip looks like this:
Call arrives from carrier via SIP → AI receptionist handles intake →
AI transfers via SIP back to client's PBX → human picks up with context
For a warm transfer, fire a webhook to your CRM before connecting. The human sees caller details on their screen before they even say hello.
Testing the Voice AI Agent with a SIP Softphone
You don’t need a real carrier to test this. A SIP softphone registered to your SignalWire space simulates the external SIP traffic perfectly. For testing purposes, the softphone plays the role of the carrier: it sends a SIP INVITE to your agent’s SIP address, just like a carrier would when forwarding a PSTN call.
- Start the agent:
python agent.py - Expose via ngrok:
ngrok http 3000 - Create the Script resource in SignalWire pointing to your ngrok URL (as described above)
- Attach a SIP address to the Script resource
- Register a softphone (Zoiper, Linphone, MicroSIP) using your SIP Credential:
- Server:
your-space.sip.signalwire.com - Username/password: from your SIP Credential
- Transport: TLS on port 5061
- Server:
- Dial the SIP address from the softphone:
receptionist@your-space.dapp.signalwire.com
The AI answers. You have a conversation. It asks your name, what you need, confirms the department, logs the call, and transfers. Watch your terminal: you’ll see [LOG] and [TRANSFER] print as the tools fire.
To verify the transfer end-to-end, create a second SIP Credential, register a second softphone as the “department desk,” and point the transfer destination at it. When the AI transfers, the second softphone rings. That’s the full BYOC (Bring Your Own Carrier) loop. No PSTN, no porting, pure SIP!
Watch the finished system in action: Demo Video
Production Considerations for SIP + Voice AI
A demo is one thing. Production is another. Here’s what matters when this goes live:
- IP whitelisting. In production, restrict which IPs can send SIP traffic to your Script resource’s SIP address. Without this, anyone who discovers the address could send calls to your agent. SignalWire supports IP-based authentication on Domain Applications for this purpose.
- TLS and SRTP. SignalWire supports encrypted SIP signaling (TLS) and encrypted media (SRTP) by default. Make sure the client’s carrier or PBX is configured to match. Unencrypted SIP over the public internet is asking for trouble — like leaving your front door open and hoping nobody walks in.
- Failover. What happens when your agent is down? Configure a fallback URL in the Script resource that serves a static SWML document — something that plays “We’re experiencing technical difficulties, please try again later” or takes a voicemail.
- Codec negotiation. Ensure the client’s carrier and SignalWire agree on audio codecs. G.711 µ-law (PCMU) is the safe default for PSTN interop. If both sides support Opus, you’ll get better audio quality at lower bandwidth.
- Monitoring. Use SignalWire’s call logs to track SIP call flow, and your agent’s
on_summarycallback to capture post-call summaries programmatically. In production, pipe these to your observability stack.
What’s Next: Multi-Agent Routing, Recording, and CRM Integration
This pattern (SIP in, AI conversation, SIP out) is the foundation for more advanced setups:
- Multi-agent routing: Use
AgentServerwithsetup_sip_routing()to map different SIP usernames to specialized agents.sip:sales@...gets the sales agent,sip:support@...gets the support agent. - Time-of-day routing: Business hours go to the AI receptionist, after hours go to voicemail or an emergency escalation agent.
- Call recording and transcription: Record the AI conversation, run post-call transcription, and push structured summaries to the client’s CRM.
- MCP integration: Connect the agent’s tools to external systems via Model Context Protocol — query databases, update tickets, check inventory, all mid-conversation.
Ready to Add AI Call Handling to Your Existing Phone Infrastructure?
WebRTC.ventures builds and deploys production voice AI systems across SignalWire and other leading voice platforms integrated with your current carrier, your existing PSTN numbers, and your team’s workflow. No porting, no carrier switching, no disruption to how your customers reach you today.
If you’re evaluating this architecture for your organization, we’re happy to walk through a technical scoping call: what the integration looks like on your stack, what it takes to go from prototype to production, and where the real complexity lives. Contact us today!
SignalWire documentation:
