> For a complete page index, fetch https://docs.synthflow.ai/llms.txt. For full documentation content, fetch https://docs.synthflow.ai/llms-full.txt. # Route SIP Calls > Route calls from your PBX, SBC, or Twilio to a Synthflow agent over SIP, either by dialing the agent's number directly or by handing off a call that is already live. Your phone system can send a call to a Synthflow agent with a SIP `INVITE`. Use this when your system, not a SIP trunk, decides which calls reach the agent. To trunk a whole carrier or PBX into Synthflow instead, see [Connect over SIP](/connect-over-sip). | Method | Use it when | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | [Direct dialing](#direct-dialing) | Your system sends a new inbound call straight to the agent. | | [Live call handoff](#live-call-handoff) | Your system connects the call first, for example to run its own voicemail detection, then passes it to the agent with variables. | ## Prerequisites * A [custom number](/phone-numbers#adding-custom-numbers) assigned to an inbound agent. Synthflow treats every call to that number as inbound. * Firewall rules for your region's [signaling and media addresses](/ip-allow-lists#region-addresses). * An [API key](/authentication), to hand off a live call or to set up the number through the API. Send every `INVITE` to your region's SIP address on port `32681`, listed in [SIP addresses](/connect-over-sip#sip-addresses). Format the number in E.164, for example `sip:+12065551234@sip.us.synthflow.ai:32681`. ## API setup You can create the custom number and the agent in the app, or with two API calls. First, import the number. If your carrier or PBX requires Synthflow to register, set `uac_enabled` to `true` and provide `trunk_username` and `trunk_pwd`. ### Request POST [https://api.synthflow.ai/v2/custom-numbers](https://api.synthflow.ai/v2/custom-numbers) **`Import Custom Number`** ```curl Import Custom Number curl -X POST https://api.synthflow.ai/v2/custom-numbers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "workspace_id": "1710107690998x536152705164378100", "phone_number": "+33782990580", "friendly_name": "My Custom Number", "provider_name": "custom", "termination_uri": "sip.example.com" }' ``` **`Import Custom Number`** ```python Import Custom Number import requests url = "https://api.synthflow.ai/v2/custom-numbers" payload = { "workspace_id": "1710107690998x536152705164378100", "phone_number": "+33782990580", "friendly_name": "My Custom Number", "provider_name": "custom", "termination_uri": "sip.example.com" } headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` **`Import Custom Number`** ```go Import Custom Number package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.synthflow.ai/v2/custom-numbers" payload := strings.NewReader("{\n \"workspace_id\": \"1710107690998x536152705164378100\",\n \"phone_number\": \"+33782990580\",\n \"friendly_name\": \"My Custom Number\",\n \"provider_name\": \"custom\",\n \"termination_uri\": \"sip.example.com\"\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` **`Import Custom Number`** ```ruby Import Custom Number require 'uri' require 'net/http' url = URI("https://api.synthflow.ai/v2/custom-numbers") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{\n \"workspace_id\": \"1710107690998x536152705164378100\",\n \"phone_number\": \"+33782990580\",\n \"friendly_name\": \"My Custom Number\",\n \"provider_name\": \"custom\",\n \"termination_uri\": \"sip.example.com\"\n}" response = http.request(request) puts response.read_body ``` **`Import Custom Number`** ```java Import Custom Number import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.synthflow.ai/v2/custom-numbers") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{\n \"workspace_id\": \"1710107690998x536152705164378100\",\n \"phone_number\": \"+33782990580\",\n \"friendly_name\": \"My Custom Number\",\n \"provider_name\": \"custom\",\n \"termination_uri\": \"sip.example.com\"\n}") .asString(); ``` **`Import Custom Number`** ```csharp Import Custom Number using RestSharp; var client = new RestClient("https://api.synthflow.ai/v2/custom-numbers"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"workspace_id\": \"1710107690998x536152705164378100\",\n \"phone_number\": \"+33782990580\",\n \"friendly_name\": \"My Custom Number\",\n \"provider_name\": \"custom\",\n \"termination_uri\": \"sip.example.com\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` **`Import Custom Number`** ```swift Import Custom Number import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [ "workspace_id": "1710107690998x536152705164378100", "phone_number": "+33782990580", "friendly_name": "My Custom Number", "provider_name": "custom", "termination_uri": "sip.example.com" ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.synthflow.ai/v2/custom-numbers")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` Then create an inbound agent on that number. Wait until the number is created before you send this request. ### Request POST [https://api.synthflow.ai/v2/assistants](https://api.synthflow.ai/v2/assistants) ```curl curl -X POST https://api.synthflow.ai/v2/assistants \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "type": "outbound", "name": "Sales Assistant", "agent": { "prompt": "You are a helpful sales assistant for Acme Corp.", "greeting_message": "Hello, this is Sarah from Acme Corp. How can I help you today?", "llm": "gpt-4.1-Mini", "language": "en-US", "voice_id": "eleven_turbo_v2" } }' ``` ```python import requests url = "https://api.synthflow.ai/v2/assistants" payload = { "type": "outbound", "name": "Sales Assistant", "agent": { "prompt": "You are a helpful sales assistant for Acme Corp.", "greeting_message": "Hello, this is Sarah from Acme Corp. How can I help you today?", "llm": "gpt-4.1-Mini", "language": "en-US", "voice_id": "eleven_turbo_v2" } } headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.synthflow.ai/v2/assistants" payload := strings.NewReader("{\n \"type\": \"outbound\",\n \"name\": \"Sales Assistant\",\n \"agent\": {\n \"prompt\": \"You are a helpful sales assistant for Acme Corp.\",\n \"greeting_message\": \"Hello, this is Sarah from Acme Corp. How can I help you today?\",\n \"llm\": \"gpt-4.1-Mini\",\n \"language\": \"en-US\",\n \"voice_id\": \"eleven_turbo_v2\"\n }\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.synthflow.ai/v2/assistants") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{\n \"type\": \"outbound\",\n \"name\": \"Sales Assistant\",\n \"agent\": {\n \"prompt\": \"You are a helpful sales assistant for Acme Corp.\",\n \"greeting_message\": \"Hello, this is Sarah from Acme Corp. How can I help you today?\",\n \"llm\": \"gpt-4.1-Mini\",\n \"language\": \"en-US\",\n \"voice_id\": \"eleven_turbo_v2\"\n }\n}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.synthflow.ai/v2/assistants") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{\n \"type\": \"outbound\",\n \"name\": \"Sales Assistant\",\n \"agent\": {\n \"prompt\": \"You are a helpful sales assistant for Acme Corp.\",\n \"greeting_message\": \"Hello, this is Sarah from Acme Corp. How can I help you today?\",\n \"llm\": \"gpt-4.1-Mini\",\n \"language\": \"en-US\",\n \"voice_id\": \"eleven_turbo_v2\"\n }\n}") .asString(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.synthflow.ai/v2/assistants"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"type\": \"outbound\",\n \"name\": \"Sales Assistant\",\n \"agent\": {\n \"prompt\": \"You are a helpful sales assistant for Acme Corp.\",\n \"greeting_message\": \"Hello, this is Sarah from Acme Corp. How can I help you today?\",\n \"llm\": \"gpt-4.1-Mini\",\n \"language\": \"en-US\",\n \"voice_id\": \"eleven_turbo_v2\"\n }\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [ "type": "outbound", "name": "Sales Assistant", "agent": [ "prompt": "You are a helpful sales assistant for Acme Corp.", "greeting_message": "Hello, this is Sarah from Acme Corp. How can I help you today?", "llm": "gpt-4.1-Mini", "language": "en-US", "voice_id": "eleven_turbo_v2" ] ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.synthflow.ai/v2/assistants")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` | Field | Value | | -------------- | --------------------------------------------------------------------------- | | `type` | `inbound` | | `name` | A name for the agent | | `phone_number` | The custom number you imported | | `agent` | The agent's `llm`, `language`, `prompt`, `greeting_message`, and `voice_id` | ## Direct dialing Send the call to the agent's number at your region's SIP address. Synthflow answers it like any other inbound call. To keep a reference to the call in your own system, add your call ID in the `X-EI` header. The example below redirects a Twilio call with TwiML and passes the Twilio Call SID. **`Node.js`** ```javascript Node.js const express = require('express'); const { twiml: { VoiceResponse } } = require('twilio'); const app = express(); const SYNTHFLOW_SIP = 'sip.us.synthflow.ai:32681'; app.get('/redirect_call', (req, res) => { const { Called, CallSid } = req.query; if (!Called) { return res.status(400).send('Missing "Called" query parameter'); } const response = new VoiceResponse(); response.dial().sip(`sip:${Called}@${SYNTHFLOW_SIP}?X-EI=${CallSid}`); res.type('text/xml').send(response.toString()); }); app.listen(process.env.PORT || 3000); ``` **`Python`** ```python Python from fastapi import FastAPI, Response from twilio.twiml.voice_response import VoiceResponse app = FastAPI() SYNTHFLOW_SIP = "sip.us.synthflow.ai:32681" @app.get("/redirect_call") async def redirect_call(Called: str, CallSid: str): response = VoiceResponse() response.dial().sip(f"sip:{Called}@{SYNTHFLOW_SIP}?X-EI={CallSid}") return Response(str(response), media_type="text/xml") ``` A [before-the-call action](/pass-call-context#before-the-call-actions) can send `` to your API, so the agent starts the call with data from your system. ## Live call handoff Your system places or answers the call, runs its own logic, and then transfers the connected call to the agent. Register the call with Synthflow first, so the agent receives your variables and the call keeps one ID. ```mermaid flowchart LR A[Your system connects the caller] --> B[POST /v2/prepare_inbound] B --> C[SIP INVITE with X-EI header] C --> D[Synthflow agent continues the call] ``` ### 1. Call registration Send a `POST` to `/v2/prepare_inbound` on your region's [API base URL](/getting-started-with-your-api#base-urls), with your API key as a bearer token. ```bash curl -X POST https://api.us.synthflow.ai/v2/prepare_inbound \ -H "Authorization: Bearer $SYNTHFLOW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from_number": "+14155550199", "to_number": "+12065551234", "model_id": "your-agent-id", "workspace_id": "your-workspace-id", "custom_variables": { "customer_name": "Jane Doe", "account_id": "A12345" } }' ``` | Field | Description | | ------------------ | ------------------------------------------------- | | `from_number` | The caller's number, in E.164 format. | | `to_number` | The called number, in E.164 format. | | `model_id` | The agent ID of the inbound agent. | | `workspace_id` | Your workspace ID. | | `custom_variables` | Key-value pairs passed to the agent as variables. | The response includes two fields you need: ```json { "call_status": "registered", "synthflow_call_id": "ebe4bd96-dcb1-4abd-9aa5-686877dab061" } ``` * `call_status` is `registered` when Synthflow is ready to take the call, and `failed` during a service outage. * `synthflow_call_id` identifies the call. You send it in the next step. ### 2. SIP transfer Once the caller is connected, send an `INVITE` to the agent's number at your region's SIP address. Add an `X-EI` header with the call ID from step 1, in the exact format `S.;`, including the trailing semicolon. The number must be the one assigned to the inbound agent, in E.164 format. **`SIP`** ```text SIP INVITE sip:+12065551234@sip.us.synthflow.ai:32681 SIP/2.0 X-EI: S.ebe4bd96-dcb1-4abd-9aa5-686877dab061; ``` **`Asterisk`** ```text Asterisk same => n,Set(PJSIP_HEADER(add,X-EI)=S.;) same => n,Transfer(SIP/+12065551234@sip.us.synthflow.ai:32681) ``` **`Twilio (Node.js)`** ```javascript Twilio (Node.js) const response = new VoiceResponse(); response.dial().sip( `sip:+12065551234@sip.us.synthflow.ai:32681?X-EI=S.${synthflowCallId};` ); ``` `X-EI` can also carry your own call ID and the original caller. See [X-EI header](/pass-call-context#x-ei-header) for the full format. ## Additional context Any `X-*` header you add to the `INVITE` becomes a variable the agent can use in its prompt, actions, and transfers. See [Pass call context](/pass-call-context). > Send calls from your own phone system to a Synthflow agent over SIP.