Create an OpenAI Realtime Voice Agent
Introduction
The openaivoiceagent.cs sample connects an inbound 3CX call to an OpenAI Realtime voice session. It can greet callers, answer general questions, search the permitted 3CX directory entries, connect callers, offer voicemail or chat, and save useful caller context when that feature is enabled.
The script also includes a disabled get_department_hours custom tool that demonstrates how to register a safe AI-callable function.
This script requires a 3CX AI Edition license, a PBX build Update 10, and an OpenAI API account.
Create the Call Script in 3CX
- Sign in to the 3CX Admin Console.
- Go to Admin > Integrations > Call Scripts.
- Select +Add from Store.
- Choose openaivoiceagent.cs.
- Enter a lowercase script name without spaces, for example openaireception.
- Select how the script will run; for a receptionist, assign a dedicated DID or route the appropriate inbound trunk calls to the script.
- Select the department that owns the script.
- Confirm the selection to open the code editor.
Configure OpenAI and the Script
Add the following parameters to the PBX:
- OPENAI_API_KEY - the API key for your OpenAI project.
- OPENAI_REALTIME_MODEL - the OpenAI Realtime model.
Leave ApiKeyOverride and ModelOverride empty in the script. When these values are empty, the script automatically reads the API key and model from the PBX parameters.
Do not enter the OpenAI API key directly in the script, especially if the script will be shared, exported or published. A value configured in ApiKeyOverride or ModelOverride takes precedence over the corresponding PBX parameter.
Next, review these customer settings near the top of openaivoiceagent.cs:
Setting | Purpose | Sample value |
FallbackDestination | Route used when media or the AI session fails | 102 |
VoiceName | OpenAI voice used by the agent | Coral |
AgentName | Name presented to the provider session | Alex |
AllowAllVisibilityForTesting | Exposes all supported directory objects | true |
VisibleNumbers | Approved extensions, queues, or ring groups | 100, 102 |
VisibleDepartments | Departments the AI may search | Sales, Support |
VisibleRoles | Optional permitted roles | empty |
AgentInstructions | Company identity, behavior, and routing rules | Example Company |
AddAll() is convenient for an initial test but should normally be disabled before production. Set AllowAllVisibilityForTesting to false, then configure only the numbers, departments, and roles the agent needs.
To enable the sample custom tool, review its static response and uncomment:
RegisterExampleCustomTool();
Replace the example with a trusted data source before using it for real customer information.
Select Save to compile. Confirm that the Script output reports a successful compilation before assigning production traffic.
How It Works
- An inbound call reaches the script route point.
- The script clears and rebuilds the AI directory visibility list.
- 3CX prepares the media channel.
- The script starts an OpenAI Realtime voice session.
- The agent uses only the built-in 3CX functions and any explicitly registered custom tools.
- A successful transfer hands the caller to the selected 3CX destination.
- If media setup or the provider session fails, the script attempts the configured fallback and then plays the ERROR prompt if routing also fails.
Test the Script
- Call the assigned DID and confirm the greeting and selected voice.
- Search for an allowed extension by name and number.
- Confirm that hidden extensions cannot be searched or selected.
- Test an ambiguous directory match.
- Test transfer, unavailable-user voicemail, and chat-message behavior.
- Use an invalid provider key in a test environment and verify fallback routing.
- End the conversation naturally and confirm session cleanup.
Troubleshooting
- Provider session fails: Verify OPENAI_API_KEY, the supported realtime model, network access, licensing, and the target PBX build.
- The agent cannot find a user: Check AllowAllVisibilityForTesting, VisibleNumbers, VisibleDepartments, and VisibleRoles.
- The wrong objects are visible: Call Clear() before adding the production visibility list and avoid AddAll().
- Fallback does not work: Confirm the destination exists and can be reached from the assigned department.
- No error prompt is heard: Confirm ERROR exists in the active prompt set.
See Also
Last Updated
This guide was last updated on 9 September 2026