Voice
Voice calls are supported for US and Canada numbers. You can place a call, or decide what happens when someone calls a number you own.
We will cover these scenarios in this doc:
- Outbound
- Agentic: an LLM managing the call, having a conversation with the user, and calling custom tools or webhooks when it needs to
- Scripted: an automated voice reading a text (similar to PagerDuty)
- Inbound (no code)
- Agent: an LLM answering the call, having a conversation with the caller, and calling custom tools or webhooks when it needs to
- Scripted: an automated voice reading a text when receiving a call
- Transfer
- Disable / reject
- Inbound (with code)
- Customize the inbound behavior on the fly
Quick note about Agentic vs Scripted calls
A scripted call reads text you wrote and then the call is done. Nothing listens for an answer and nothing calls your server. Use it for a verification code, an appointment reminder, an incident alert, or an after-hours message. The voice reads the text the way an on-call alert reads a page.
An agentic call is an LLM on the phone. It has a conversation, and it calls a tool or a webhook when it needs something it does not already know: an order, a free appointment slot, or a person to transfer to. Use it when the next line depends on what the other person says.
Agents
Pick one model per call. voiceId must be a voice that model offers. Omit it for the default.
| Model | Use it when | Default voice |
|---|---|---|
openai:gpt-realtime-2.1 |
A normal back-and-forth conversation | marin |
openai:gpt-realtime-2.1-mini |
A shorter conversation with a smaller model | marin |
openai:gpt-live-1 |
You want one of the extra voices listed below | marin |
google:gemini-3.1-flash-live-preview |
You want Gemini handling the call | Puck |
xai:grok-voice-latest |
You want Grok handling the call | eve |
Voices:
- OpenAI realtime (
gpt-realtime-2.1and mini): alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar gpt-live-1: those voices, plus quartz, ripple, vesper, willow, stone, gleam, meridian, bossa, tempo, beacon, delta, cinder- Gemini: Puck, Charon, Kore, Fenrir, Aoede, Leda, Orus, Zephyr
- Grok: eve, ara, rex, sal, leo
instructions tell the agent who it is and how to handle the call. On an outbound call, opener is the first line after the other person answers. On an inbound call, greeting is the first line when your number picks up. Leave either one empty and the agent waits.
Tools and webhooks
Tools are only for agents. A script cannot call them. Omit tools when the agent should only talk.
| Tool | What it does | Use it when |
|---|---|---|
custom |
Calls your HTTPS webhook. You set name, description, and a JSON Schema for the arguments. |
The answer is in your app: an order, a booking, an account balance. |
transfer |
Connects the call to a US or Canada number. cold connects immediately. warm briefs the recipient first. |
The person on the phone asks for a human. |
knowledge |
Answers from documents you uploaded in the dashboard. | The answer is in a file: hours, a policy, a price list. |
disconnect |
Says goodbye and hangs up. | The task is finished and the agent should end the call. |
dtmf |
Sends keypad tones. | The agent has to get through a phone menu. |
mcp |
Uses the tools on a remote MCP server. | Those tools already live on an MCP server. |
weather |
Looks up the current weather for a city the caller names. | The call is about the weather. |
A custom tool can speak while your webhook runs. beforeSpeech is said if this turn has not spoken yet. After slowSpeechAfterSeconds, slowSpeech is said once if the webhook is still running.
{ type: 'custom', name: 'order_status', description: 'Look up an order by id', parameters: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] }, url: 'https://example.com/voice/order-status', beforeSpeech: 'One moment, I am checking that order.'}Outbound
An outbound call dials a US or Canada number. The call is initiated from your code.
Examples use the Node SDK (npm install pingram). The same methods exist on the Python, Go, PHP, Java, C#, and Ruby SDKs.
import { Pingram } from 'pingram';
const pingram = new Pingram({ apiKey: 'pingram_sk_...' // region: 'ca' when the account is in Canada});
pingram.voice.call(...) // details belowThe call response includes trackingId (to later track the call or control it further) and websocket (for live audio and transcripts).
Recording and transcription are on unless you set privacy. Pass privacy: { record: false, transcribe: false } on the config to turn either off.
Agentic Outbound Example
The model runs the conversation and calls a tool when it needs one. opener is optional. Omit it, or pass "", and the agent waits for the other person to speak first.
voicemailAction defines what to do when we detect voicemail (more on that below).
You can use the dashboard (Voice/Outbound) to test various scenarios and generate the outbound code without guessing these fields.
const call = await pingram.voice.call({ to: '+14165551234', config: { type: 'agent', opener: 'Hi {{name}}, this is Acme calling about your appointment tomorrow.', agent: { instructions: 'You confirm appointments for Acme. Be brief. If they want a person, transfer the call.', model: { model: 'openai:gpt-realtime-2.1', voiceId: 'marin' }, tools: [ { type: 'transfer', destinations: [ { phoneNumber: '+1xxxxxxxxxx', condition: 'The caller asks to speak with a person', transferMode: 'cold' } ] } ] }, voicemailAction: 'message', voicemailMessage: 'Please call us back to confirm your appointment.' }, variables: { name: 'Sam' }});Scripted Outbound Example
The call reads the text in order. Each line is a Say step. {{name}} tags are filled from variables.
const call = await pingram.voice.call({ to: '+14165551234', config: { type: 'script', steps: [ { verb: 'Say', text: 'Hi {{name}}, your verification code is {{code}}.' } ] }, variables: { name: 'Sam', code: '123456' }});Voicemail
When an answering machine picks up, the voicemailAction parameter chooses what happens. The default is continue, which basically continues to reading the script.
| Value | What happens |
|---|---|
hangup |
End the call |
message |
Speak voicemailMessage, then hang up |
continue |
Read the script |
Inbound (no code)
On the Voice page, open Inbound and choose what a number you own should do.
Agent
The number answers with an LLM. Set the greeting, the instructions, the model, and any tools from the section above. The greeting is the first line. Leave it empty and the agent waits for the caller. If you leave the greeting unset, it is “Hi, how may I help you today?”.
Use this when someone calling your number might ask a question, and the agent may need a webhook or a transfer to finish the call.
Scripted
The number answers and reads the text you wrote. Same idea as an outbound script: one or more lines, spoken in order, then the call is done.
Use this for hours, a holiday closure, or any message that does not depend on what the caller says.
Transfer
The caller is connected to another US or Canada number. The person who answers sees the original caller’s number.
Use this when every call to that number should go to a person, with no script and no agent.
Disable
The number rejects inbound calls. Outbound calls are unaffected.
Inbound (with code)
Saved inbound behavior is the default. A VOICE_INBOUND webhook can replace it for one call, before the call is answered and fully customize the inbound behaviour.
Subscribe from Webhooks in the dashboard. Payloads are signed the same way as your other Pingram webhooks. Respond within a few seconds. An empty body, or no endpoint, keeps the saved behavior.
{ "eventType": "VOICE_INBOUND", "trackingId": "01a1468a-c3d5-7154-b317-6c22ccd765e9", "from": "+14165551234", "to": "+14165550000"}Respond with the inbound config for this caller. That is how you pass instructions or context that depend on who is calling. A script response reads text. An agent response can include instructions, a model, and tools.
{ "type": "agent", "greeting": "Hi Sam, I see your appointment is tomorrow.", "agent": { "instructions": "This is an important client. Ask what they need and immediately transfer.", "model": { "model": "openai:gpt-realtime-2.1" } }}VOICE_CONNECTED fires when the call is connected. VOICE_ENDED fires when it hangs up. Both include eventType, trackingId, from, and to.
Transcript and recording
const { call } = await pingram.voice.getCall(trackingId);call.timeline is the transcript. call.recordingUrl is the recording when recording was on. The same detail is on the call in the dashboard.