OpenAI Voice
Answer phone calls and browser calls with an OpenAI realtime model that has a Squid agent behind it
The OpenAI Voice connector's functionality
The OpenAI Voice connector puts an OpenAI Realtime speech-to-speech model on the line:
- Natural conversation. The realtime model hears and speaks directly, with no transcription step in between: it handles greetings, small talk and confirmations itself, and can be interrupted.
- The Squid agent as its brain. Whenever the caller asks for information or wants something done, the model calls the agent through an
ask_agenttool. The agent answers with its knowledge bases, connectors and AI functions, and the model relays the answer in its own voice. - Phone calls, as the voice engine of a Twilio phone line.
- Browser calls straight from a web page over WebRTC, with no phone number involved, through
squid.ai().voice().startWebCall(). - Languages. The call starts in the line's language and can follow the caller into selected languages, or any language.
- Transcripts. Both sides of the call are transcribed and recorded like on every other engine.
Audio flows between the caller and OpenAI. Squid only steers the call: it sets it up with the agent's instructions, runs the agent when the model asks, and hangs up when the model ends the call.
Before you begin
- An OpenAI organization with access to the Realtime API.
- The project the calls should run in and its id (
proj_…), shown in the project's settings. Phone calls reach OpenAI through the project's SIP endpoint,sip:<projectId>@sip.api.openai.com. - An API key created in that project.
- For phone calls, a webhook endpoint registered in the project, described below. Browser calls need no webhook.
Adding the OpenAI Voice connector to your Squid application
-
Navigate to the Connectors tab in the Squid Console.
-
Click Available Connectors.
-
Find the OpenAI Voice connector, and select Add Connector.
-
Provide the following configuration details:
Connector ID: A string that uniquely identifies the connector in your code, such as openai_voice.
API Key: An API key of the OpenAI project. Stored as a secret.
Project ID: The OpenAI project id (proj_…).
Webhook Secret: Optional but recommended. The signing secret (whsec_…) of the webhook endpoint you register in the project. With it, Squid verifies that incoming-call webhooks come from OpenAI; without it, any request to the webhook URL is trusted.
Default Model: Optional. The realtime model for agents that pick none. Defaults to the newest realtime model, gpt-realtime-2.1.
Default Voice: Optional. The voice for agents that pick none. Defaults to marin.
The Advanced section holds Webhook Base URL, the public base URL OpenAI reaches this application at (only needed behind a proxy, a tunnel or a custom domain), and API Base URL, OpenAI's API base URL (default https://api.openai.com).
The connector configures nothing per agent. Which agents answer with it, with which model and voice, is decided on each agent's phone line.
Using OpenAI Voice on a phone line
-
Add the Twilio connector to the application, if you have not yet.
-
In Agent Studio, add the Twilio ability to the agent (or open it), and pick a Phone number.
-
Set the Voice engine to OpenAI voice and choose the OpenAI Voice connector to use.
-
Choose the OpenAI model (the account's realtime models, newest first) and the OpenAI voice.
marinandcedarare the newest voices;alloy,ash,ballad,coral,echo,sage,shimmerandverseare also available. -
Set the Language the call starts in and, under Languages, whether the caller may switch to selected languages or to any language.
-
Write the Greeting and choose the Opening line: the greeting as written, or a line the agent composes once the call connects, so a known caller is greeted by name.
-
Save. The status line reports that the number rings this agent, and shows the webhook URL to register in the OpenAI project. Register it as described next, once per connector.
The full list of options, including allowInterruptions and interruptionSensitivity which are set from code, is in the Twilio connector's phone line settings.
Registering the incoming-call webhook
OpenAI announces every call that reaches the project's SIP endpoint by posting a realtime.call.incoming event to a webhook of the project. Squid answers that call from the webhook, so the webhook must point at your application:
-
Get the webhook URL: it is shown in the Twilio ability's status line after saving, and in
provisioning.openAi.webhookUrlon the agent's phone line. It looks likehttps://[APP_ID].[REGION].squid.cloud/webhooks/openAiVoiceIncoming?integrationId=openai_voice. From code,sipTarget()on the client returns it together with the SIP URI. -
In the OpenAI platform, open the project's Settings > Webhooks and create an endpoint with that URL, subscribed to the
realtime.call.incomingevent. -
Copy the endpoint's signing secret and save it as the connector's Webhook Secret.
One endpoint serves every agent that uses the connector: the call's SIP headers name the agent. A project can only announce calls to the endpoints registered in it, so a second application using the same project needs its own endpoint, and a development and a production application using one project each need theirs.
The webhook URL of a backend started with squid start includes your developer id and is public, so a development endpoint can point at it. Keep in mind that every endpoint of the project receives every call: a call for an agent your development application does not have is rejected by it, and answered by the application whose agent it is.
How a phone call flows
- Twilio receives the call on the agent's number and asks the application how to handle it. The Twilio connector bridges the call to the OpenAI project's SIP endpoint, with SIP headers naming the agent, the Twilio call and the Twilio connector.
- OpenAI posts
realtime.call.incomingto the registered webhook. The OpenAI Voice connector verifies the signature, reads the agent from the headers and checks that the agent's phone line uses this connector; a call it does not recognize is rejected. - Squid accepts the call with the line's model, voice and language rules, instructions that describe the Squid agent, and two tools:
ask_agentandend_call. It speaks the greeting, or has the agent compose the opening line. - During the call, every
ask_agentcall runs one turn of the Squid agent, with the call's context and memory, and hands the answer back to the model to speak. Caller and agent turns are recorded as they are transcribed. - When the caller says goodbye, the model calls
end_callwith a short farewell. Squid lets the farewell play out, then hangs up both the OpenAI and the Twilio legs.
An outbound call placed with placeCall follows the same path from step 1 on once the callee answers, with the call's purpose added to the model's instructions.
What the realtime model does and what the agent does
The model is told to answer greetings, small talk and confirmations itself, and to call ask_agent for everything it cannot know on its own, passing the caller's request with every detail. That keeps the conversation fluid while the agent stays the single source of truth. Consequences worth knowing:
- Put facts, rules and tools on the Squid agent, not in the phone line. The model never sees your knowledge bases directly; it asks.
- The agent's instructions apply to what it answers; how the model speaks is governed by Squid's built-in phone instructions and the line's language settings.
- A tool that takes long makes the model say "one moment" first; keep AI functions used on calls fast.
Browser calls
The same connector serves calls from a web page, no Twilio and no phone number needed. The browser opens the microphone, Squid brokers a WebRTC session with OpenAI and runs the agent behind it, and the agent's voice plays in the page:
const call = await squid.ai().voice().startWebCall({ agentId: 'front-desk' });
call.onTurn((turn) => console.log(`${turn.role}: ${turn.text}`));
call.onEnd(() => console.log('The call ended'));
Users may start calls under the agent's security rules, exactly as they may chat with it; the agent then runs as that user. Every setting of a browser call, from the voice to the languages and the greeting, defaults to the agent's phone line when it has one with the OpenAI engine, and to this connector otherwise, and can be overridden per call. See Voice agents for the full API.
Using the OpenAI Voice connector from code
npm install @squidcloud/openai-voice-client
import { SquidOpenAiVoiceClient } from '@squidcloud/openai-voice-client';
const openAiVoice = new SquidOpenAiVoiceClient(this.squid, 'openai_voice'); // your connector id
// Where phone calls go, and the webhook URL to register in the OpenAI project.
const { sipUri, webhookUrl } = await openAiVoice.sipTarget();
// The realtime models the account can put on a call, newest first, and the voices.
const { models } = await openAiVoice.listModels();
const { voices } = await openAiVoice.listVoices();
| Method | Executable name | Description |
|---|---|---|
sipTarget() | openAiVoiceSipTarget | The SIP URI calls are sent to and the webhook URL to register. Needs the API key. |
listModels() | openAiVoiceListModels | Realtime models of the account, newest first. |
listVoices() | openAiVoiceListVoices | Voices of the realtime models. |
Phone lines are set up through the Twilio connector, and calls are answered by Squid itself; these methods are the building blocks the Twilio connector and the console use.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| The phone rings and is never answered | The project has no webhook endpoint for realtime.call.incoming, or it points elsewhere. Register the URL from the ability's status line, see registering the webhook. |
The application log shows INVALID_OPENAI_WEBHOOK_SIGNATURE | The connector's Webhook Secret is not the signing secret of the endpoint that sent the call. Each endpoint has its own secret; copy it again after creating or rotating the endpoint. |
| The log shows "Call … names no agent of this integration; rejecting it" | The call's SIP headers name an agent whose phone line does not use this connector, for instance a line saved with another OpenAI Voice connector id, or a call from another application sharing the project. |
| The ability reports "Agent … is not wired up for OpenAI yet" | The line was saved before the OpenAI connector was chosen. Save the ability again. |
OPENAI_VOICE_INTEGRATION_REQUIRED on a browser call | The agent has no phone line with the OpenAI engine and the application has several OpenAI Voice connectors. Pass integrationId to startWebCall(). |
| The model says it will check and then answers with an error or nothing | ask_agent failed: the agent's turn raised an error. Look at the application log for the agent's failure, such as a broken AI function or a model provider error. |
| The call cuts off a moment after the farewell | Expected: the call is hung up once the farewell has played out (or after 15 seconds when OpenAI reports no playback). |