Connect OpenAI's Realtime API to an Indian phone number over SIP
Answer calls to an Indian phone number with OpenAI's Realtime API through its SIP connector, with no media server in between. The origination URI with your project ID, TLS and SRTP handled by RingTrunk, the incoming-call webhook your application accepts, and the failures to recognise.
Updated 24 September 2026
In short: Set your RingTrunk trunk's origination URI to proj_…@sip.api.openai.com:5061;transport=tls and accept OpenAI's realtime.call.incoming webhook in your application; calls to your Indian number are then answered by an OpenAI Realtime model, with TLS and SRTP handled by RingTrunk.
OpenAI's Realtime API can answer phone calls itself, through its SIP connector. A call to your RingTrunk number is delivered to sip.api.openai.com, OpenAI sends your application a webhook, and your application accepts the call with the model, instructions and voice to use. There is no LiveKit, media server or SIP stack of your own in between. OpenAI's reference for the connector is its SIP guide.
What you need
- A RingTrunk trunk with an Indian number attached.
- An OpenAI project and its project ID, which starts with
proj_and is shown in the project's settings on platform.openai.com. - An HTTPS endpoint that OpenAI can reach, for the incoming-call webhook.
1. Point your trunk at your OpenAI project
The origination URI tells RingTrunk where to deliver calls to your number. On the trunk's Origination tab, add:
[email protected]:5061;transport=tlsReplace proj_abc123 with your project ID. Each part of the URI does a job:
- `proj_abc123@` tells OpenAI which project the call belongs to. Without a real project ID there, OpenAI refuses every call at once.
- `:5061;transport=tls` makes RingTrunk deliver the call over TLS and encrypt the audio with SRTP, which OpenAI's connector requires. Nothing else is needed on your side for encryption.
Through the API, the same entry is {"uri": "sip.api.openai.com:5061;transport=tls", "user": "proj_abc123", "priority": 10, "enabled": true} in PUT /api/v1/trunks/{sid}/origination-uris. For OpenAI's EU data residency, use sip-eu.api.openai.com as the host.
OpenAI finds your project from the ID in the URI, so there is no IP address to allow on the OpenAI side.
2. Accept calls in your application
OpenAI completes a call only after your application accepts it. In the OpenAI dashboard, under Settings, Project, Webhooks, create a webhook for the realtime.call.incoming event that points at your HTTPS endpoint, and keep the signing secret, which is shown once. For every call, OpenAI posts that event and your handler accepts the call:
import os
import httpx
from fastapi import FastAPI, Request
from openai import OpenAI
client = OpenAI(webhook_secret=os.environ["OPENAI_WEBHOOK_SECRET"])
app = FastAPI()
@app.post("/openai-webhook")
async def incoming_call(request: Request):
# Verifies the signature and parses the event
event = client.webhooks.unwrap(await request.body(), request.headers)
if event.type != "realtime.call.incoming":
return {"ok": True}
sip = {h.name.lower(): h.value for h in event.data.sip_headers}
dialled = sip.get("p-called-party-id", "") # which of your numbers was called
caller = sip.get("from", "")
async with httpx.AsyncClient() as http:
await http.post(
f"https://api.openai.com/v1/realtime/calls/{event.data.call_id}/accept",
headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
json={
"type": "realtime",
"model": "gpt-realtime-2.1",
"instructions": "You answer calls for Example Co. Keep replies short.",
"audio": {"output": {"voice": "marin"}},
},
)
return {"ok": True}Accept promptly. A call that nothing accepts rings for about 15 seconds and is then dropped by OpenAI. To give the model tools or to follow the conversation, open a WebSocket to wss://api.openai.com/v1/realtime?call_id=<call_id> after accepting. OpenAI retries a webhook it could not deliver, so ignore an event whose webhook-id header you have already handled.
3. Call your number
Dial your Indian number from any phone. The call reaches OpenAI, your webhook fires, and the model starts speaking once your handler accepts. The call appears in the portal under Calls with its duration and result.
What your application receives
The INVITE that RingTrunk sends to OpenAI carries your project ID in the request URI, and the number that was dialled in a P-Called-Party-ID header:
INVITE sip:[email protected] SIP/2.0
From: <sip:[email protected]> ; the caller
To: <sip:[email protected]>
P-Called-Party-ID: <sip:[email protected]> ; your number that was dialledOpenAI passes the INVITE's headers to your webhook in data.sip_headers. Read P-Called-Party-ID to tell your numbers apart when several of them point at one project, and From for the caller's number. Treat both as call metadata, not as proof of identity.
Outbound calls and transfers
OpenAI's SIP connector receives calls; it does not place them. OpenAI's documentation states that outbound calls are not supported on this path. For an agent that also dials out on your Indian number, run the model behind a platform that places SIP calls through your trunk, such as LiveKit or Vapi.
Call transfer through OpenAI's refer endpoint is not supported on RingTrunk. To end a call from your application, use OpenAI's hangup endpoint.
GPT-Live
OpenAI's GPT-Live model answers SIP calls at the same address. With GPT-Live, your application accepts the call with GPT-Live's incoming-call event and accept endpoint instead of the Realtime ones. The RingTrunk side, the origination URI in step 1, is the same.
When it fails
| Symptom | Cause | Fix |
|---|---|---|
| Every call fails at once, before it rings | The origination URI has no project ID before the @, or the ID is not a real proj_ ID. | Use proj_…@sip.api.openai.com:5061;transport=tls with the project ID from your OpenAI dashboard. |
| The call rings for about 15 seconds, then ends; the call log says "Not accepted by your SIP server" | Nothing accepted the call: the project has no realtime.call.incoming webhook, or your handler did not call the accept endpoint. | Create the webhook on the project named in the origination URI, and accept every call from the handler. |
| The webhook arrives, but accepting returns an error | A field in the accept request was rejected, such as a model name OpenAI has retired, or the API key belongs to a different project. | Read the error in the response, and use an API key from the project in your origination URI. |
Working example
The webhook server on this page, with tests that sign webhook payloads the way OpenAI does, is at github.com/Zingaro-Ai/ringtrunk-openai-sip-example (MIT).
More SIP responses and what they mean are on Troubleshooting. How origination URIs, the user part and TLS destinations work in general is on Receiving calls.