Connect a Photon number to an ElevenLabs voice agent over SIP and verify your first incoming call.
Call your Photon number and have your agent answer. Photon connects the phone
network to your agent over SIP; ElevenLabs runs the voice conversation.
You do not stream phone audio through the messaging HTTP endpoint.This walkthrough starts with incoming calls only. You do not need to
enable outbound calling or generate an outbound SIP password to receive your
first call.
A Photon project you can configure, with an eligible SMS number or
dedicated iMessage or WhatsApp number. Shared lines cannot take calls.
An ElevenLabs account and a voice agent you can publish.
A separate phone to call your Photon number.
Number availability, subscription requirements, and call usage can incur costs.
Review plans and billing and the purchase confirmation
before acquiring a number. If you already have an eligible number, reuse it.
1
Prepare an agent that can hold a conversation
In ElevenLabs, create an agent using its
agent quickstart.
Choose its voice, set a first message such as Hello, I’m your test agent.
What would you like to try?, and give it a small job, such as answering
questions about a fictional coffee shop.Use Preview in the agent editor. Ask a question, hear its response,
and publish the version you want callers to reach. Fix the agent here
before troubleshooting the phone connection.
2
Find your Photon number and its Voice profile
Select your Photon project and open Voice. Find your number under
Lines. If it is missing, confirm it is voice-capable; a shared iMessage
assignment is not an eligible number.If there is no default profile, select Set up default, choose SIP,
and select Create profile. If your number already uses a profile, open
that profile instead.
Create the default SIP profile before adding other profiles. Dashboard preview with sample data.
The default applies to numbers without an additional profile. An
additional profile affects only numbers assigned to it. Check the number’s
assignment and the profile’s Lines count before editing: the change
affects new calls on every line using that profile.
3
Import the number into ElevenLabs
In ElevenLabs, open Deploy → Phone Numbers, select Import number,
then From SIP Trunk. Enter a label you recognize and your Photon
number in international format, including + and the country code.Configure Inbound Configuration for the calls arriving from Photon.
Use Allowed for media encryption to match Photon’s Auto setting.
If you enable inbound SIP authentication in ElevenLabs, save that
username and password: you must enter the same pair in Photon next.These are ElevenLabs inbound credentials, not a Photon API key or a
Photon outbound SIP password. Leave outbound configuration disabled for
this incoming-only test.Save the imported number, then select your published agent in its
Agent dropdown. If Branch appears, choose the version or traffic
split you intend callers to reach. A number with No agent will not
run your conversation.For the provider’s current import fields and security options, see
ElevenLabs SIP trunking.
4
Point Photon at the agent's SIP destination
Return to the Photon Voice profile used by the number. Under Inbound
calls, select Enable inbound if needed. Configure:
Setting
Value for this ElevenLabs connection
SIP address when enabling, or Destination when editing
sips:sip.rtc.elevenlabs.io:5061
Transport
TLS
SIP credentials
The inbound username and password configured in ElevenLabs, if required there. Otherwise leave them unset.
Media encryption
Auto, paired with Allowed in ElevenLabs for this initial test.
The ElevenLabs receiving destination with TLS selected. Dashboard preview; a saved address alone does not verify call delivery.
Complete Enable inbound for a new configuration. For an existing
destination, edit the field and move focus out of it to save. Confirm the
saved address and TLS remain visible. If required by the provider,
add its inbound credentials using Add or Replace beside the
credential row.TLS protects SIP signaling. Requiring encrypted audio is a separate
policy; if you require it, enable Require encrypted calls in Photon
and Required media encryption in ElevenLabs, with matching support in
both directions.
5
Call the agent from another phone
Dial your Photon number. Listen for the greeting, ask your test question,
and confirm the agent hears you and responds audibly. End the call.In Photon, check Voice → Recent calls for the attempt and its final
status. In ElevenLabs, check the conversation for the selected agent.
A saved SIP destination is not proof of a working call: verify audio in
both directions and that the call ends normally.
The voice conversation runs in the provider, but you can inspect its Photon
routing with any published API client. Complete
project setup with platforms:read, copy the
Profile id from this Voice profile, and set:
Save the example as voice.ts, voice.py, src/main.rs, or main.go in the
directory you prepared in project setup. These are API-client calls, not
audio streaming or a replacement for your provider’s agent code.
import { Photon } from "@photon-ai/api";const { PHOTON_API_KEY, PHOTON_PROJECT_ID, PHOTON_VOICE_PROFILE_ID } = process.env;if (!PHOTON_API_KEY || !PHOTON_PROJECT_ID || !PHOTON_VOICE_PROFILE_ID) { throw new Error("Set PHOTON_API_KEY, PHOTON_PROJECT_ID, and PHOTON_VOICE_PROFILE_ID");}const photon = new Photon({ headers: { Authorization: `Bearer ${PHOTON_API_KEY}` },});try { const profile = await photon.projects.platforms.getVoiceProfile({ path: { id: PHOTON_PROJECT_ID, profileId: PHOTON_VOICE_PROFILE_ID }, }); console.log(profile.configuration.inbound?.destinationUri ?? "Inbound is not enabled");} catch { console.error("Could not read the Voice profile"); process.exitCode = 1;}
import osfrom photon_api import Photon, PhotonErrorfrom photon_api.rpc_generated import GetVoiceProfileInputtry: with Photon(headers={"Authorization": f"Bearer {os.environ['PHOTON_API_KEY']}"}) as photon: profile = photon.projects.platforms.get_voice_profile( GetVoiceProfileInput.model_validate({ "path": { "id": os.environ["PHOTON_PROJECT_ID"], "profileId": os.environ["PHOTON_VOICE_PROFILE_ID"], } }) ) inbound = profile.configuration.inbound print(inbound.destinationUri if inbound is not None else "Inbound is not enabled")except PhotonError: raise SystemExit("Could not read the Voice profile") from None
use photon_ai_api::{Credential, PhotonClientBuilder, SecretString};#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = PhotonClientBuilder::new() .credential( "projectApiKey", Credential::Bearer(SecretString::from(std::env::var("PHOTON_API_KEY")?)), ) .build()?; let response = client .get_voice_profile( std::env::var("PHOTON_PROJECT_ID")?, std::env::var("PHOTON_VOICE_PROFILE_ID")?, ) .await .map_err(|_| "Could not read the Voice profile")?; match &response.inner().configuration.inbound { Some(inbound) => println!("{}", inbound.destination_uri), None => println!("Inbound is not enabled"), } Ok(())}
package mainimport ( "context" "fmt" "log" "os" photonapi "github.com/photon-hq/api/packages/go")func required(name string) string { value := os.Getenv(name) if value == "" { log.Fatalf("Set %s", name) } return value}func main() { client := photonapi.NewClient(photonapi.WithAPIKey(required("PHOTON_API_KEY"))) profile, err := client.Projects.Platforms.GetVoiceProfile( context.Background(), &photonapi.GetVoiceProfileParams{ ID: required("PHOTON_PROJECT_ID"), ProfileID: required("PHOTON_VOICE_PROFILE_ID"), }, ) if err != nil { log.Fatal("Could not read the Voice profile") } if profile.Configuration.Inbound == nil { fmt.Println("Inbound is not enabled") return } fmt.Println(profile.Configuration.Inbound.DestinationURI)}
node voice.ts
python voice.py
cargo run --bin photon-first-agent
go run main.go
The returned destination confirms what is saved in Photon, not whether the
remote agent answered. Keep the phone-call check above as your success test.