Skip to main content
OpenAI Realtime API integration with Vobiz SIP trunking Connect inbound calls on a Vobiz number directly to OpenAI’s Realtime API. The AI answers the call and holds a natural, low-latency conversation with the caller.
Scope: inbound calls only. A caller dials a Vobiz number and is answered by the AI agent.
Source code: vobiz-ai/Vobiz-Openai-Realtime - the reference Flask service used throughout this guide (app.py, requirements.txt, .env.example).

How it works

Vobiz does not proxy the audio. The SIP INVITE is handed to OpenAI, which terminates the media directly with the caller. Your application server only performs control-plane work over HTTPS and a WebSocket - it never handles RTP.
Because OpenAI terminates the media, VobizXML verbs such as <Stream> are not used in this integration, and audio resampling is not required.

Requirements

Network requirements

OpenAI publishes the following media CIDR blocks. Any firewall between Vobiz and OpenAI must permit bidirectional UDP with them:

Regional endpoints

Step 1: Create the origination URI

Console: SIP → Inbound → Origination URIs (console.vobiz.ai/app/sip/in/uri)
The port :5061 is mandatory.OpenAI accepts SIP only on 5061. Without an explicit port the URI resolves to the SIP default (5060), where OpenAI is not listening. The call is refused back to the carrier before it reaches OpenAI, and the CDR shows hangup_disposition: send_refuse with terminated_to: inbound_carrier.
Substitute your own Project ID, found at platform.openai.com → Settings → Project → General.
The response returns the new URI’s id, which you pass as primary_uri_uuid when creating the trunk. transport is not settable on Create Origination URI - select TLS in the console.

Step 2: Create the inbound trunk

Console: SIP → Inbound → Trunks (console.vobiz.ai/app/sip/in/trunks)
secure must be enabled, and it is distinct from transport.
  • transport controls signalling encryption (TLS).
  • secure controls media encryption (SRTP).
OpenAI requires SRTP. If transport is TLS but secure is left disabled, the call completes signalling, answers, and bills normally - but no audio passes. The symptom is a connected, billed call with total silence, and elevated packet_loss in the CDR. Setting secure: true resolves it.

Step 3: Assign the phone number

Attach the DID that callers will dial to the trunk from Step 2.
A successful assignment returns 204 No Content. See Assign Number to Trunk.

Step 4: Register the OpenAI webhook

platform.openai.com → Settings → Webhooks → Create Copy the signing secret (whsec_…) shown once at creation. If it is lost, delete the webhook and recreate it.

Step 5: Configure the service

Environment variables

The repo ships a .env.example. Copy it with cp .env.example .env and fill in your values:
.env
Notes on the keys:
  • Variable names are case-sensitive and must be uppercase.
  • Names must not contain spaces. OPENAI WEBHOOK_SECRET is not a valid key and is silently ignored by python-dotenv, which surfaces later as a signature-verification failure.
  • OPENAI_PROJECT_ID must be identical to the user part of the Origination URI. A mismatch routes the INVITE to a different project, and the webhook never fires.
  • Your Vobiz AUTH_ID and AUTH_TOKEN are only needed for the provisioning calls in Steps 1-3. The service itself talks to OpenAI, not to the Vobiz API.

Dependencies

The repo’s requirements.txt:
requirements.txt

Step 6: Handle the call

Three actions are required when realtime.call.incoming arrives. The excerpts below are from app.py in the sample repo.

Verify the webhook signature

Return 400 on failure. OpenAI signs each delivery with webhook-id, webhook-timestamp and webhook-signature headers.

Accept the call

Returning 200 to the webhook does not accept the call. Acceptance is a separate REST call:
Codecs are negotiated by OpenAI. Audio format fields are not set for SIP calls.

Open the control WebSocket

This channel carries session events, transcripts and tool calls. Send response.create to make the agent speak first:

Enable caller transcription (optional)

Caller-side transcription is off by default. Enable it after the socket opens:
Caller speech then arrives as conversation.item.input_audio_transcription.completed, and agent speech as response.output_audio_transcript.done. Both carry a transcript field.

Transferring to a human agent

Escalation uses SIP REFER, issued by OpenAI and routed by the Vobiz trunk.

Give the agent a transfer tool

Issue the REFER when the tool fires

Listen for response.function_call_arguments.done, then:
OpenAI sends a SIP REFER down the Vobiz trunk, which creates a new leg to the target and bridges the caller to it. The transferred leg appears in CDR as a separate outbound record carrying the trunk ID. Transfer target requirements
  • The target must be reachable and able to receive an INVITE.
  • An endpoint that can place calls is not necessarily able to receive them. A registered SIP user requires an active contact binding in the registrar; without one, the INVITE cannot be delivered and the call clears instead of transferring.
  • tel: targets take E.164 form, for example tel:+919888888888.

Other call controls

Running the service

Development

ngrok issues a new domain on each restart. The webhook URL in the OpenAI console must be updated whenever it changes.

Production

  • Terminate HTTPS with a valid certificate - OpenAI does not deliver webhooks over plain HTTP.
  • Keep SKIP_SIGNATURE_VALIDATION=false.
  • Gunicorn workers do not share memory. Any per-call state must live in shared storage such as Redis if it is read outside the worker that accepted the call.

Verifying the integration

Webhook delivery

platform.openai.com → Settings → Webhooks → Send test event, selecting realtime.call.incoming. A correctly configured service returns 200. Test events carry an empty data object and no live SIP session, so an accept attempt returns 404 call_id_not_found. The service should acknowledge this with 200 rather than an error - repeated 5xx responses count against the endpoint’s delivery health.

Live call

Dial the assigned DID. The agent answers within a few seconds and speaks the configured greeting.

Behaviour reference

Useful CDR fields

Next steps