This guide shows you how to set up an OPENAI_REALTIME provider trunk on Infobip, configure call routing, and build a simple Node.js webhook server that receives OpenAI events and controls the session via WebSocket. By the end, you’ll have a caller talking to an AI agent through Infobip Voice/WebRTC. Note: This is a minimal dev setup: it’s meant to work, not to survive production.
- An OpenAI project
- An Infobip OPENAI_REALTIME provider trunk
- A Call Routing rule that sends calls to that trunk
- A small Node.js webhook that accepts the call and logs events
Before you start, you'll need a few things:
- An Infobip account (you can sign up here, if you don't already have one)
- An OpenAI account with Realtime API access and an OpenAI API key (you can see more details here)
- Node.js installed, version 14 or higher works well
- ngrok for exposing your local server to the internet
First things first. You need to create an OpenAI Realtime project. Once you've done that, grab your Project ID. It looks something like proj_abc123xyz. This ID is what OpenAI uses in the SIP URI to route calls to the right project.
You can find this in your OpenAI dashboard under your project settings.
Now we need to tell Infobip how to reach OpenAI. You can do this by creating a provider trunk of type OPENAI_REALTIME. There are two ways you can do this:
Send a POST request to create the trunk:
curl -X POST https://api.infobip.com/calls/1/trunks \
-H "Authorization: App YOUR_INFOBIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "PROVIDER",
"name": "My OpenAI Provider Trunk",
"location": "FRANKFURT",
"internationalCallsAllowed": false,
"channelLimit": 10,
"billingPackage": {
"packageType": "METERED"
},
"provider": {
"type": "OPENAI_REALTIME",
"projectId": "my_projectId"
}
}'You'll get back something like this:
{
"id": "sip-trunk-id-abc123",
"type": "PROVIDER",
"name": "My OpenAI Provider Trunk",
"provider": {
"type": "OPENAI_REALTIME",
"projectId": "my_projectId"
}
}Make sure to save your id. We will need it later to guide the incoming calls to the trunk.
If you prefer clicking buttons, do this:
- Go to the SIP Trunking page in the Portal
- Click Create SIP Trunk
- Choose OpenAI Realtime from dropdown
- Enter your OpenAI Project ID
- Configure the other settings as needed
Note: Infobip doesn't validate your OpenAI project ID when you create the trunk. Make sure you've got the right one.
For OPENAI_REALTIME provider trunks, Project IDs are unique per Infobip account. You can create multiple trunks with the same Project ID on your account, but that Project ID can't be used on a different account. Don't worry if your Project ID leaks. It's tied to your account.
Now you need to tell Infobip which calls should go to your OpenAI trunk. Let's say you want all WebRTC calls to go there.
curl -X POST https://api.infobip.com/callrouting/1/routes \
-H "Authorization: App YOUR_INFOBIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "OpenAI Provider Trunk Route",
"criteria": [{
"value": {
"identity": ".*",
"type": "WEBRTC"
}
}],
"destinations": [{
"value": {
"sipTrunkId": "your-trunk-id",
"type": "SIP"
},
"type": "ENDPOINT"
}],
"transferOnly": false
}'Replace your-trunk-id with the ID you got from Step 2.
Or just use the Call Routing page in the Portal if that's more your style.
Since we're developing locally but need to receive webhooks from OpenAI, we'll use ngrok to expose our local server to the internet.
Download ngrok from the official website. Extract it somewhere convenient. You can add it to your PATH if you want to run it from anywhere, but that's optional.
Verify it works:
ngrok versionIf you don't have an account already, you need to signup. You can do it here. It's free. Go to the authtoken page in your dashboard. Copy your authtoken. Then configure it:
ngrok config add-authtoken YOUR_AUTHTOKEN_HEREHere's the good part. The free tier of ngrok gives you a static domain that doesn't change. This is perfect because we don't want to keep updating our webhook URL.
Go to the domains page in your ngrok dashboard. Click Create Domain. You'll get something like magical-platypus-12345.ngrok-free.app. Save this. It's yours to keep.
Now run ngrok with your static domain:
ngrok http 3000 --domain=your-domain.ngrok-free.appYou should see output showing:
Forwarding https://your-domain.ngrok-free.app -> http://localhost:3000
Keep this terminal open. Also check out the web interface at http://127.0.0.1:4040. It's super useful for debugging because you can see all the HTTP requests coming in.
- The routing rule uses
.*for WebRTC identity. That matches everything. Great for demos. Dangerous for production. - We use ngrok for local development only.
In another terminal, try this:
curl https://your-domain.ngrok-free.appYou'll get a 502 Bad Gateway error. That's actually good. It means ngrok is working but nothing's listening on port 3000 yet. That's what we'll build next.
Time to build the Node.js server that will receive OpenAI events and control the session.
mkdir openai-webhook-server
cd openai-webhook-server
npm init -y
npm install express axios dotenvQuick breakdown. Express handles HTTP requests. axios lets us make API calls to Infobip. dotenv keeps your API keys safe.
touch .envAdd your credentials:
OPENAI_API_KEY=your_openai_api_key_here
INFOBIP_API_KEY=your_infobip_api_key_here
PORT=3000And make sure you don't commit this:
echo ".env" >> .gitignoreHere's the actual webhook server. Create a file called server.js:
const express = require('express');
const axios = require('axios');
// Configuration
require('dotenv').config();
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const PORT = process.env.PORT || 3000;
const OPENAI_API_URL = "https://api.openai.com/v1/realtime/calls"
const app = express();
app.use(express.json());
// Helper function to accept call
async function acceptCall(callId) {
try {
const response = await axios.post(
`${OPENAI_API_URL}/${callId}/accept`,
{
type: 'realtime',
instructions: 'You are a support agent. Speak in English unless the user requests a different language.',
// Feel free to customise these instructions based on your needs
model: 'gpt-realtime'
},
{
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Call ${callId} accepted. Status: ${response.status}`);
return response.data;
} catch (error) {
console.error(`Error accepting call ${callId}:`, error.message);
throw error;
}
}
// Main webhook endpoint for incoming calls
app.post('/openai/incomingcall', async (req, res) => {
console.log('Received incoming call request:', JSON.stringify(req.body, null, 2));
console.log('Headers:', JSON.stringify(req.headers, null, 2));
const { type, data } = req.body;
if (!data || !data.call_id) {
return res.status(400).json({ error: 'Missing call_id in request' });
}
const callId = data.call_id;
try {
// Accept the call
await acceptCall(callId);
res.status(200).json({
success: true,
message: 'Call accepted successfully',
callId: callId
});
} catch (error) {
console.error('Error handling incoming call:', error.message);
res.status(500).json({
error: 'Failed to accept call',
message: error.message
});
}
});
app.get('/health', (req, res) => res.json({ status: 'ok' }));
// Start server
app.listen(PORT, () => {
console.log(`OpenAI Service running on port ${PORT}`);
console.log(`Webhook endpoint: http://localhost:${PORT}/1/openai/incomingcall`);
console.log(`Health check: http://localhost:${PORT}/health`);
});node server.jsYou should see output like this:
OpenAI Webhook Server Started
Listening on port 3000
Health check: http://localhost:3000
Webhook endpoint: http://localhost:3000/openai/incomingcall
Ready to receive events
In another terminal, try these:
# Test locally
curl http://localhost:3000
# Test via ngrok
curl https://your-domain.ngrok-free.appBoth should return this JSON:
{
"status":"ok",
"message":"OpenAI Webhook Server Running",
"activeSessions":0
}Now tell OpenAI where to send events when calls come in. For this minimal setup, we are only going to subscribe to realtime.call.incoming.
curl -X POST https://api.openai.com/v1/realtime/webhooks \
-H "Authorization: Bearer YOUR_OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-H "OpenAI-Beta: realtime=v1" \
-d '{
"url": "https://your-domain.ngrok-free.app/openai/incomingcall",
"events": [
"realtime.call.incoming"
]
}'You'll get back something like this:
{
"id": "webhook_abc123",
"url": "https://your-domain.ngrok-free.app/openai/incomingcall",
"events": ["realtime.call.incoming"],
"created_at": 1234567890
}Or go to the OpenAI Realtime Console. Find the Webhooks section. Add your URL there. Subscribe to realtime.call.incoming.
You can test your config by pressing that little button in webhook setup that says "Send test event".
You can also test manually:
curl -X POST https://your-domain.ngrok-free.app/openai/incomingcall \
-H "Content-Type: application/json" \
-d '{
"type": "realtime.call.incoming",
"data": {
"call_id": "call_test123"
}
}'Time to see it all work together. We'll make a WebRTC call that goes through Infobip's call routing to your OpenAI trunk.
Here's what happens when you make a call:
Your Browser (WebRTC call)
↓
Infobip Platform (receives call)
↓
Call Routing (matches your routing rule)
↓
OpenAI Provider Trunk (connects to OpenAI via SIP)
↓
OpenAI Realtime API (creates the AI session)
↓
OpenAI sends webhook event to your server
↓
You can now talk to the AI
Make sure everything is running:
- ngrok is running with your static domain
- Your Node.js server is running
- The OpenAI trunk is created with your project ID
- Call routing is configured to route to your trunk
- OpenAI webhook is pointing to your ngrok URL
The easiest way is using Infobip Call Link. You can create a Call Link via our API:
curl -L -g 'https://api.infobip.com/call-link/1/links' \
-H 'Authorization: {authorization}' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"destination": {
type: "APPLICATION",
callsConfigurationID: "CALL_ROUTING"
},
"validityWindow": {
"oneTime": false
}
}'As a response, you will get somethink like this:
{
"id": "r9wpIY",
"url": "https://call-link.com/r9wpIY"
}Now you just enter that URL into your browser, and you are good to go.
Or if you prefer clicking buttons you can
go to Call Link Management in the Portal. Click Create Call Link. Set the destination type to Application. Set the Application ID to CALL_ROUTING. Create it and copy the URL. Configure other settigns to your liking. Enter the URL you received into your browser and voila.
Open that URL in your browser. Grant microphone permissions. Hit the call button.
In your Node.js logs:
Received event: realtime.call.incoming
Incoming call: call_abc123xyz456
Session URL: wss://api.openai.com/v1/realtime?session=sess_789xyz
Connected to OpenAI session: call_abc123xyz456
Session configured
Session created: call_abc123xyz456
Session updated: call_abc123xyz456
In your browser, you should be connected. You can talk to the AI. Try saying Hello or asking it something.
You can watch what's happening in a few places.
Your Node.js terminal shows WebSocket events. The ngrok web interface at http://127.0.0.1:4040 shows HTTP requests. Infobip Call Logs show call details.
No webhook events? Check that ngrok is running and the URL hasn't changed. Verify the webhook is configured in OpenAI with the right URL. Look at ngrok's web interface to see if requests are coming in.
WebSocket won't connect? Check your OpenAI API key is valid. Make sure you have credits in your OpenAI account. Look for errors in your server logs.
No audio? Check the session configuration, especially the turn_detection settings. Verify the call is actually connecting through Infobip by checking the call logs. Make sure the call routing is pointing to the right trunk.
Here's what you've built:
┌───────────────┐
│ Browser/Phone │
│ (WebRTC/SIP) │
└───────┬───────┘
│
↓
┌─────────────────────┐
│ Infobip Platform │
│ Calls Config │
│ Call Routing │
└─────────┬───────────┘
│
↓
┌─────────────────────┐
│ OpenAI Provider │
│ Trunk (SIP) │
└─────────┬───────────┘
│
↓
┌─────────────────────┐
│ OpenAI Realtime API │
│ (AI Agent) │
└─────────┬───────────┘
│
↓ (webhook)
┌─────────────────────┐
│ ngrok tunnel │
└─────────┬───────────┘
│
↓
┌─────────────────────┐
│ Your Node.js Server │
│ Receives webhooks │
│ Controls session │
└─────────────────────┘
Now that you've got the basics working , you can do more.
Customize the AI behavior by changing the instructions in the session config. Add call recording to save conversations. Implement routing logic to handle different types of calls differently. Add error handling and retry logic for production use. Deploy to a real server instead of using ngrok. AWS, Google Cloud, and Azure all work well. Integrate with your CRM to personalize the AI's responses based on caller data. Set up analytics to track when transfers happen and why.
Port 3000 already in use? Kill it:
lsof -ti:3000 | xargs kill -9Or just use a different port in your .env file.
ngrok session limit? Kill it and restart:
pkill ngrokCan't refresh VSCode to see changes? Close and reopen the file. Or use Ctrl+Z then Ctrl+Y. Or right-click the file and select Revert File.
Here are some useful links:
- Infobip Calls API documentation
- OpenAI Realtime API documentation
- ngrok documentation
- Infobip Portal
That's it. You should now have a working setup where people can call in and talk to an OpenAI agent. Infobip handles the call infrastructure. Your webhook server manages the session.





