Safeguard
Supply Chain Security•

Voice AI Agents Explained: Pipelines, Realtime APIs and Security

Voice agents chain speech to text, a model and speech, or use one realtime model over WebRTC or SIP. How they work and how to secure tools and callers.

Safeguard Security Team
40 min read

A voice AI agent is software that listens to a person, decides what to do with a language model, can call tools, and speaks back. It is built either as a cascaded pipeline (speech to text, a text model, text to speech) or around a single realtime speech model. Secure it by treating every word heard as untrusted input.

This post goes one level deeper than our pieces on the voice AI latency budget, the AG-UI protocol, the Agent Client Protocol and how a smart speaker is made. Latency gets one short section because the sibling post covers it. The depth goes to architecture, realtime transports, telephony, turn taking, tool calls, and the security and legal controls a voice agent needs before it answers a real phone line. Vendor behaviour comes only from the vendor's own documentation; protocol behaviour comes from the RFCs and the W3C specification.

Key takeaways

  • A cascaded pipeline turns speech into text, runs a text agent and turns the reply back into speech. A speech to speech model hears audio and answers in audio inside one session. OpenAI's documentation also describes a full duplex voice model that delegates reasoning and tools to a separate backend.
  • Realtime voice APIs use three transports: WebRTC for browsers and apps, WebSocket for server owned audio, and SIP for phone calls. Each puts the trust boundary in a different place.
  • Turn taking is a classifier problem: voice activity detection decides when the caller has finished, and barge in handling decides what happens when they talk over the agent.
  • Tools are where voice agents become dangerous. The tool set, its permissions and its confirmations matter more than the prompt.
  • The main risks are prompt injection by speech or through tool results, trusting caller ID, voice cloning, recording without consent, and personal data piling up in transcripts.
  • In the US, FCC ruling 24-17 (February 2024) treats AI generated voices as "artificial" voices under the TCPA, so outbound AI calls need prior express consent. In the EU, AI Act Article 50 requires telling people they are talking to an AI system.
  • A safe first build is read only: an allow list of tools, redaction before storage, confirmation checked in code, a human handoff, and one log record per turn.

What is a voice AI agent?

A voice agent is a loop. It captures audio, works out when the person has finished a turn, understands the request, decides on an action, possibly calls a tool, then speaks, while still listening in case the person interrupts. What makes it hard is that the loop runs on live audio, against a clock, with a person who can talk at any moment.

A chat user sends a complete message and waits. A caller pauses, says "um", changes their mind mid sentence and expects a reply within a human sized gap. The agent has to decide when a turn has ended without a send button, start speaking before the full answer is ready, stop the instant it is interrupted, and keep the record honest about what the caller actually heard.

OpenAI's voice agents guide frames the key design choice as "how speech connects to reasoning and tools". That is also the security question: wherever speech becomes text, and wherever text becomes an action, is where your checks go.

Architecture 1: the cascaded pipeline

The cascaded (or chained) pipeline has three stages:

  1. Speech to text (STT). A recognition model turns audio into a transcript, often streaming partial words.
  2. The agent. A text model reads the transcript and history, decides what to do, calls tools and writes a reply.
  3. Text to speech (TTS). A synthesis model turns the reply into audio, ideally streaming the first sentence while the rest is written.

OpenAI's guide recommends this path when you want to "inspect or transform text" between stages, and gives the reason: you can store the transcript, run policy checks before the agent responds, call internal systems, and generate speech only once the workflow reaches an approved answer. Every stage boundary is a place where you can see plain text and redact, classify, log or stop it.

The cost is delay at each hop and lost information. A transcript drops tone, hesitation and overlap; a doubtful "yes" and a confident one look identical.

Architecture 2: the speech to speech realtime model

A speech to speech model takes audio in and produces audio out within one session. OpenAI's Realtime API guide says the model "works directly with audio, maintains conversation state, and can call tools", and recommends it for agents that need "barge-in, low first-audio latency, natural turn taking, and realtime tool use." The documented browser flow: your server creates an ephemeral client secret, the frontend creates a session, the session connects over WebRTC in the browser or WebSocket on the server, and the agent handles audio turns, tools and interruptions inside that session.

This removes the hand offs between three models and keeps paralinguistic signal. What it gives up is the blocking text checkpoint. Transcripts still arrive as events (the GA interface uses names such as response.output_audio_transcript.delta), but alongside the audio, not before it. A policy check on the transcript runs in parallel with speech the caller is already hearing.

Architecture 3: a live voice front end with a delegated backend

OpenAI's documentation also describes GPT-Live, which "can listen and speak at the same time, a capability called full duplex." The live model handles the spoken interaction and delegates reasoning and tool use to a backend: one you run (client delegation) or an OpenAI hosted Responses model (Responses delegation). The guide states that "in both modes, your application controls permissions and business records."

For security, this restores a clear seam between the voice and the thing that acts, and that seam is where authorization belongs. The documentation for this pattern is recent, so check the current guide before relying on specific event names or limits.

How do realtime APIs carry audio?

OpenAI's getting started guide says a session "connects over WebRTC in the browser or WebSocket on the server", and its telephony guide adds SIP. Each choice changes where audio is decoded, who manages playback and where secrets live.

WebRTC: browsers and mobile apps

The W3C specification "WebRTC: Real-Time Communication in Browsers" is a W3C Recommendation dated 13 March 2025. It defines RTCPeerConnection for connection setup and media, media tracks for microphone and speaker audio, and RTCDataChannel for messages alongside the media.

In a voice agent, the browser gets microphone access, adds the track to a peer connection, opens a data channel for JSON events and creates a Session Description Protocol (SDP) offer. OpenAI's WebRTC guide exchanges that offer for an answer through POST /v1/realtime/calls, either from your server with a standard API key or from the browser with an ephemeral key your server minted through POST /v1/realtime/client_secrets. Audio then flows on the media track and events on the data channel.

Media encryption comes built in: RFC 5764 (May 2010) keys SRTP through DTLS, and RFC 3711 (March 2004) defines SRTP's confidentiality, message authentication and replay protection. Browsers grant microphone access only in a secure context with user permission, which is why OpenAI lists HTTPS or localhost as a prerequisite.

The credential is your job. Never ship a long lived API key to a browser. Your server, which knows who the user is, creates a short lived secret tied to one session configuration and hands only that to the client. The server decides model, instructions and tools; the client cannot widen them.

WebSocket: server owned audio

A WebSocket (RFC 6455, December 2011) is one full duplex connection upgraded from HTTP, with no media layer. Audio travels as base64 chunks inside JSON events, such as input_audio_buffer.append up and response.output_audio.delta down.

That suits servers that already hold audio: a contact center bridge, a media server, a test harness. It also moves work onto you. OpenAI's conversation guide notes that with WebRTC and SIP "the server manages a buffer of output audio", but with a WebSocket "the client manages audio playback, and thus must stop playback and handle truncation." Authentication is a bearer token on the upgrade request and transport security is TLS, so WebSocket connections belong on servers.

SIP: phone calls

RFC 3261 (June 2002) defines the Session Initiation Protocol, which creates, modifies and terminates sessions. SIP carries call setup (INVITE, responses, ACK, BYE); the audio flows separately as RTP, ideally SRTP. A carrier or SIP trunk connects the telephone network to your endpoint.

OpenAI's telephony guide describes two routes:

ConnectionWho carries audioWhat your application does
Direct SIPThe phone provider exchanges audio directly with the model providerWebhooks, session configuration, call decisions, business logic
Server audio bridgeYour application relays audio over WebRTC or WebSocketBoth connections, event translation, playback and call lifecycle

In direct SIP, an incoming call fires a webhook (realtime.call.incoming for the Realtime API) carrying a call ID and the SIP headers. Your backend verifies the signature, applies its rules, then calls an accept endpoint with the session configuration or a reject endpoint with a SIP status; the docs say reject defaults to 603 Decline. The GPT-Live version of the guide states that SIP signalling uses TLS and that it "requires SRTP for call audio."

After acceptance your server can attach a sideband: a WebSocket to the same session (the Realtime docs show wss://api.openai.com/v1/realtime?call_id=...) carrying events and commands while SIP carries audio. The server controls guide says this lets the server "execute private tools" while keeping "API keys and tool credentials on your server."

Two sentences in the telephony guide matter most. "Treat data.sip_headers as untrusted caller metadata, not authorization." And assign one handler to each action "so that duplicate webhook deliveries or events observed on multiple connections don't execute tools twice." One is about identity, the other about idempotency.

Codecs, sample rates and keypad tones

  • PCM16 at 24 kHz is the raw mono format in OpenAI's WebSocket examples.
  • G.711 mu-law (audio/pcmu in the Realtime config) is the classic 8 kHz telephone codec. Audio that crossed the phone network was probably narrowband at some point, which caps recognition quality.
  • Opus (RFC 6716, September 2012) is the voice codec RFC 7874 requires WebRTC endpoints to implement; RFC 6716 says it "supports all bitrates from 6 kbit/s to 510 kbit/s".
  • DTMF keypad tones travel in band or as RTP events under RFC 4733 (December 2006). The GPT-Live sideband emits transport.dtmf.received, which the docs describe as a notification only.

DTMF matters for security because it is an input channel that is not speech. Account numbers and one time codes typed on the keypad need not pass through the speech model at all.

The latency budget, briefly

People expect a reply in about the gap a human leaves between turns; our latency post covers the research and per stage budget. In short, a cascaded pipeline pays for endpointing, recognition, time to first token, synthesis and network hops in sequence, so it must stream every stage. OpenAI's voice agents guide recommends measuring audible response time and comparing the median and 95th percentile across similar calls. Every check you add sits in that budget: blocking checks (authorization, confirmation) are worth it, while observing checks (transcript classification for logs) can run asynchronously.

How do turn detection, barge in and interruptions work?

Voice activity detection and endpointing

Voice activity detection (VAD) decides whether audio contains speech; endpointing decides when a turn is over. A caller silent for 600 ms while thinking may still be mid turn.

OpenAI's VAD guide documents two modes. server_vad "automatically chunks the audio based on periods of silence", tuned by threshold (0 to 1; higher needs louder audio, which helps in noise), prefix_padding_ms (audio kept from before detected speech) and silence_duration_ms (how much silence ends a turn). The documented example uses 0.5, 300 ms and 500 ms. semantic_vad "chunks the audio when the model believes based on the words said by the user that they have completed their utterance", with an eagerness of low, medium, high or auto (auto equals medium). Trailing speech gets a longer timeout than a definitive statement.

VAD is on by default in speech to speech sessions and can be turned off, in which case your application commits each turn with input_audio_buffer.commit.

Barge in and truncation

Barge in means the caller speaks while the agent is speaking, and a good agent stops at once. When VAD detects speech the server emits input_audio_buffer.speech_started; with interrupt_response enabled the reply is cut off.

The subtle part is the record. If the model generated 20 seconds of reply and the caller interrupted after 4, the transcript of what the agent "said" is wrong unless it is cut to what was played. On WebRTC and SIP the server "will automatically truncate unplayed audio when there's a user interruption." On a WebSocket the client must stop playback, note the played duration and send conversation.item.truncate.

This is a security issue. Suppose the reply was "I can cancel that order. Before I do, please confirm the last four digits," and the caller interrupted after the first sentence. Without truncation, your logs and the model's own context say the agent asked for confirmation that the caller never heard. An audit draws the wrong conclusion, and the model may act as if a confirmation step happened.

Push to talk and manual turns

For high consequence actions automatic turn detection can be the wrong tool. The conversation guide describes push to talk: on button down, clear the input buffer, send response.cancel and output_audio_buffer.clear; on button up, commit and request a response. It is slower but unambiguous, and background audio such as a television cannot start a turn.

How do voice agents call tools and keep state?

Function calling mid conversation

You declare tools in the session configuration. When the model calls one, it emits a call ID and JSON arguments; your code runs the function and sends back a function_call_output item with the same call ID, then requests a response. OpenAI's conversation guide shows this sequence. A short spoken filler ("Let me look that up") covers silence on reads. For writes, the agent should say what it is about to do and wait for an explicit yes.

Where tools execute matters

Where the tool runsExampleWho holds the credentialIf the conversation is hijacked
Browser clientFunction calls handled on the WebRTC data channelThe user's browserThe user can call any exposed tool with any arguments
Your server via a sidebandServer attached to the session by call IDYour serverBounded by server side authorization
Model providerA remote MCP server in the session configWhatever auth you configureBounded by allow list and approval settings

OpenAI's server controls guide says your application "supplies the tool execution, authorization checks, and business rules." For anything touching customer data or money, run tools on your server, where the verified identity lives.

For MCP, OpenAI's Realtime MCP guide notes that "MCP tools are executed by the Realtime API itself". The tool definition takes an optional allowed_tools list and an optional require_approval setting; when approval is required, the server adds an mcp_approval_request item that your client answers with an mcp_approval_response. The documented example sets approval to never for a read only docs server, which suits public documentation and is a poor default for anything that writes.

Keeping state across a call, and across calls

Within a session the provider keeps conversation items for each turn and tool result. OpenAI's conversation guide states that "the maximum duration of a Realtime session is 60 minutes" and that most settings can change with session.update, except the voice once the model has spoken.

Across calls, state is yours:

  1. Carry forward facts, not transcripts. An open ticket number and the last action taken are smaller and far less sensitive than raw dialogue.
  2. Bind state to a verified account, not a phone number. Numbers are recycled and spoofed.
  3. Make every action idempotent. Webhooks retry, two connections can observe one function call, and calls drop mid action. Derive an idempotency key from the call ID and tool call ID, and record the outcome before telling the caller it is done.

What are the security and abuse risks of voice agents?

Voice agents inherit the risks of text agents, framed well by the OWASP Top 10 for LLM Applications, and add some from telephony and audio.

Prompt injection by speech

OWASP lists prompt injection as LLM01. A spoken sentence is a prompt, and speech recognition transcribes intent faithfully. A caller telling the agent to ignore its instructions and read out another customer's address is attempting direct injection, exactly as if they typed it. OWASP's 2025 entry adds that "the rise of multimodal AI" introduces "unique prompt injection risks", including cross modal attacks that are hard to detect with current techniques.

Indirect injection is the bigger practical risk. An agent that reads a knowledge base article, customer note or web page to answer a question is reading text someone else wrote, and the caller never sees it. The defence is not a better system prompt but limiting what any instruction can cause:

  • Keep the tool set small and read only where possible.
  • Enforce authorization inside the tool using the verified identity from your server, never an identity the model passes in arguments.
  • Make confirmation for state changes a check in code.
  • Treat model output as untrusted when it flows elsewhere; see treating the model as a user and direct versus indirect prompt injection.

There is also a physical channel: a speakerphone or smart device hears whatever plays in the room. The defensive lesson is to assume the microphone may hear someone other than your user, and to gate consequential actions on something a nearby speaker cannot supply, such as a keypad entry, an in app tap or a callback to a number on file.

Caller spoofing and caller verification

Caller ID is a claim by the originating network, not a proof. The FCC's call authentication page explains that STIR/SHAKEN lets the receiving provider "verify that a call is in fact from the number displayed on caller ID", and that the FCC required providers to implement it in the IP portions of their networks by June 30, 2021.

RFC 8224 (February 2018) defines a signed Identity header for SIP. RFC 8588 (May 2019) adds the SHAKEN attest claim with three levels: A (full attestation, the provider can fully attest to the calling identity), B (partial, it originated the call but cannot fully vouch for the number) and C (gateway, the call entered from elsewhere). Even level A says only that a provider vouches its customer may use the number, not who holds the phone, and attestation does not always survive to your endpoint.

So: caller ID can route a call, never authenticate one. NIST SP 800-63B (revision 4) treats the telephone network as a restricted channel for out of band authentication and tells verifiers to weigh risk indicators such as "device swap, SIM change, number porting". Practical options are a one time code from your app entered on the keypad, a callback to the number on file, or authentication inside a logged in app session that hands verified identity to the voice session through your server. Knowledge questions read aloud are weak, since answers often sit in breached data.

Voice cloning and impersonation, in both directions

Your agent's voice. If it could be taken for a human or a specific real person, you invite deception complaints and legal exposure. EU AI Act Article 50 requires systems that interact directly with people to be designed so people are informed they are dealing with an AI system, unless that is obvious. One sentence at call start covers it.

Callers' voices. If any process treats "sounds like the account holder" as verification, synthetic speech defeats it. Never use voice similarity as the sole factor for anything consequential. Our post on deepfake social engineering covers the human side.

Tool permissions and excessive agency

OWASP's LLM06, Excessive Agency, names the root causes as "excessive functionality; excessive permissions; excessive autonomy." Its mitigations read like a voice checklist: minimize the extensions an agent can call and what each can do, run them with least privilege in the user's context, and "require user approval" for high impact actions. More in our excessive agency post.

In voice, the approval is itself spoken, on the same channel that might carry an injected instruction. A sound confirmation has three parts: the agent reads back the exact action and key parameters, the caller confirms, and your code checks the confirmation against a pending action ID it issued, requiring a non speech factor for high value actions.

PII in transcripts and recordings

A voice agent produces raw audio, partial and final transcripts, model context, tool arguments, tool results and logs, any of which may hold names, addresses, account numbers or health details. OWASP's LLM02 covers disclosure through model output; for voice, the larger risk is usually your own storage.

  • Raw audio is the most sensitive and least useful after the call. Keep it only with a legal basis and a purpose, briefly.
  • Transcripts should be redacted before long term storage or analytics; redaction after storage means the unredacted copy existed.
  • Tool arguments and results are the most structured personal data in the system. Log shape and outcome, not contents.
  • Card numbers should never be spoken. Collect them on the keypad through a path that bypasses the model and the recording, or hand off to a payment flow built for card data.

Secrets, webhooks and cost

Long lived API keys stay on servers; clients get ephemeral session secrets. Incoming call webhooks must be signature checked (OpenAI's SDK examples call client.webhooks.unwrap with the raw body and headers) and deduplicated by webhook ID. Voice sessions bill by time and tokens, so rate limit session creation per authenticated user and cap session length well below the provider maximum.

What do regulators say about AI voice calls and recording?

This is general information, not legal advice.

The FCC's 2024 declaratory ruling on AI voices

The FCC adopted Declaratory Ruling FCC 24-17 on 2 February 2024 and released it on 8 February 2024 (CG Docket No. 23-362). It confirms that the TCPA's restrictions on "artificial or prerecorded voice" encompass current AI technologies that generate human voices, so such calls require "the prior express consent of the called party" absent an emergency purpose or exemption. It names voice cloning as within the existing prohibition and took effect on release. The chair's attached statement notes that State Attorneys General can pursue these calls under the law.

Practically, a person choosing to call your agent is a different case from your agent calling consumers. Outbound AI voice calls need the consent the TCPA requires for artificial voice calls, and records that prove it.

Call recording consent

US federal law, 18 U.S.C. 2511(2)(d), makes it lawful for a person not acting under color of law to intercept a communication where they are a party to it or "where one of the parties to the communication has given prior consent", unless the purpose is criminal or tortious. Some states require consent from all parties, and interstate calls can fall under the stricter rule. Elsewhere, data protection law usually requires a lawful basis and clear notice. The safe default: announce recording and AI involvement at the start of every call and log that consent event.

Disclosure that the caller is talking to AI

EU AI Act Article 50(1) requires providers to design systems that interact directly with people so those people are informed they are interacting with an AI system, unless obvious from context; Article 50(2) adds marking duties for synthetic audio. Under Article 113, Article 50 applies from 2 August 2026. Check for later amendments and guidance.

Cascaded pipeline versus speech to speech: a comparison

PropertyCascaded STT, text agent, TTSSpeech to speech realtime modelLive voice front end with delegated backend
LatencyHighest by default; needs streaming at every stageLowest hand off cost; one sessionVoice stays responsive while backend work runs, per the provider's docs
ControllabilityHigh: inspect and change text between stagesMedium: transcripts arrive alongside audioHigh at the backend seam
Where policy checks fitBefore the agent and before TTS, blockingOn tool calls; transcript checks run in parallelIn the backend, before tools run
Cost profileThree meters (STT, model, TTS)One audio session meterVoice meter plus backend model meter
LoggingNatural: every stage produces textEvent stream; must handle truncationTwo logs to join
RedactionEasy before storage and before the modelOn transcripts; the model already heard the audioBefore backend tools
Paralinguistic signalLost at STTKeptKept in the voice layer
Best fitRegulated workflows, strict audit, high value actionsConversational help, low consequence actionsExisting text agents that need a voice

No column is universally right. A useful pattern is to let a realtime model own the conversation while tools, authorization and records live on your server behind a sideband or delegation boundary.

Threats and controls at a glance

ThreatWhere it entersControl that works
Prompt injection, direct or indirectCaller's words, tool results, documentsSmall tool set, server side authorization, confirmation in code
Background or played audioSpeakerphone, room devicesKeypad or app confirmation for consequential actions
Caller ID spoofingSIP From and identity headersNever authenticate on caller ID; out of band verification
Voice cloningSynthetic caller voiceNo voice similarity as a sole factor
Duplicate executionRetried webhooks, multiple observersIdempotency keys, one owner per side effect
Leaked API keyClient codeEphemeral, session scoped secrets
PII sprawlAudio, transcripts, logsRedact before storage, short retention
Unconsented outbound AI callsYour dialerConsent records; FCC 24-17

Step by step: build a read only voice agent with redaction, confirmation and per turn logging

This walkthrough designs a phone support agent that looks up order and ticket status, can make one change (a callback request) only after confirmation, hands off to a human on request, and logs one record per turn. The policy code is provider neutral; provider calls follow OpenAI's documented Realtime API. Adapt names to your stack.

Step 1: write the tool inventory before the prompt

If a tool is not on this list, the agent does not get it.

# tools.yaml : the only tools the voice agent may call
tools:
  get_order_status:
    kind: read
    requires_verified_caller: true
    args: { order_id: "^[A-Z0-9]{8}$" }
  get_ticket_status:
    kind: read
    requires_verified_caller: true
    args: { ticket_id: "^T[0-9]{6}$" }
  request_callback:
    kind: write
    requires_verified_caller: true
    requires_confirmation: true
    args: { window: "^(morning|afternoon|evening)$" }
  transfer_to_human:
    kind: handoff
    requires_verified_caller: false

Refunds, address changes and password resets are absent on purpose. Those go to a person until the agent has earned trust on reads.

Step 2: configure the session on your server, not the client

For a SIP call this is the body of the accept request; for a browser it is baked into the ephemeral secret.

{
  "type": "realtime",
  "model": "gpt-realtime-2.1",
  "instructions": "You are the support line voice assistant for Example Co. In your first sentence, say you are an automated AI assistant and that the call is recorded. You can look up orders and tickets and request a callback. You cannot change accounts, take payments or reset passwords; offer a transfer to a person for those. Never read out full account numbers.",
  "audio": {
    "input": {
      "turn_detection": { "type": "server_vad", "threshold": 0.5, "prefix_padding_ms": 300, "silence_duration_ms": 500 }
    },
    "output": { "voice": "marin" }
  },
  "tools": [
    { "type": "function", "name": "get_order_status", "description": "Look up one order for the verified caller.", "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"] } },
    { "type": "function", "name": "get_ticket_status", "description": "Look up one ticket for the verified caller.", "parameters": { "type": "object", "properties": { "ticket_id": { "type": "string" } }, "required": ["ticket_id"] } },
    { "type": "function", "name": "request_callback", "description": "Propose a callback. The system will ask the caller to confirm.", "parameters": { "type": "object", "properties": { "window": { "type": "string", "enum": ["morning", "afternoon", "evening"] } }, "required": ["window"] } },
    { "type": "function", "name": "transfer_to_human", "description": "Transfer the call to a person.", "parameters": { "type": "object", "properties": {} } }
  ]
}

The model name is an example from OpenAI's documentation at the time of writing. The instructions are a usability aid, not a control; the controls are in the next steps.

Step 3: verify and deduplicate the incoming call webhook

# webhook.py : accept inbound calls only after signature and dedupe checks
from flask import Flask, request, abort
from openai import OpenAI
import requests, os

app = Flask(__name__)
client = OpenAI()        # reads OPENAI_API_KEY and the webhook secret from the environment
seen_webhooks = set()    # use a shared store with expiry in production

@app.post("/voice/webhook")
def incoming():
    try:
        event = client.webhooks.unwrap(request.data, request.headers)  # raises on a bad signature
    except Exception:
        abort(400)
    wh_id = request.headers.get("webhook-id")
    if wh_id in seen_webhooks:
        return "", 200   # duplicate delivery
    seen_webhooks.add(wh_id)
    if event.type != "realtime.call.incoming":
        return "", 200
    call_id = event.data.call_id
    # SIP headers are untrusted caller metadata: routing hints only.
    requests.post(
        f"https://api.openai.com/v1/realtime/calls/{call_id}/accept",
        headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
        json=load_session_config(), timeout=10,
    )
    start_sideband(call_id)   # step 4
    return "", 200

Step 4: run tools on your server behind one policy gate

Attach a sideband by call ID and send every function call through one gate that checks the inventory, argument patterns, verification state and confirmation.

# gate.py : one place where every tool call is allowed or refused
import re, uuid, time, yaml
TOOLS = yaml.safe_load(open("tools.yaml"))["tools"]

class CallState:
    def __init__(self, call_id):
        self.call_id = call_id
        self.verified_account = None   # set only by your verification flow
        self.pending = None            # the one action awaiting confirmation

def check_args(spec, args):
    allowed = spec.get("args", {})
    if set(args) - set(allowed):
        return "unexpected arguments"
    for name, pattern in allowed.items():
        if not re.fullmatch(pattern, str(args.get(name, ""))):
            return f"invalid {name}"
    return None

def gate(state, tool_name, args, tool_call_id):
    spec = TOOLS.get(tool_name)
    if spec is None:
        return {"refused": "unknown tool"}
    if spec.get("requires_verified_caller") and not state.verified_account:
        return {"refused": "caller not verified", "next": "offer_verification"}
    problem = check_args(spec, args)
    if problem:
        return {"refused": problem}
    if spec.get("requires_confirmation"):
        state.pending = {"id": str(uuid.uuid4()), "tool": tool_name, "args": args,
                         "tool_call_id": tool_call_id, "expires": time.time() + 60}
        return {"needs_confirmation": True,
                "say": f"I can request a callback in the {args['window']}. Shall I go ahead? Say yes or press 1."}
    return {"allowed": True}

The account each tool acts on is state.verified_account, set by your server; the model never supplies it. Strict argument patterns mean injected text cannot become a free form query.

Step 5: make confirmation a state machine, not a sentence

# confirm.py : run only the pending action, once, if confirmed in time
import time

def on_confirmation(state, source, value):
    p = state.pending
    if not p or time.time() > p["expires"]:
        state.pending = None
        return {"say": "That request has expired. What would you like to do?"}
    ok = (source == "dtmf" and value == "1") or \
         (source == "speech" and value.strip().lower() in {"yes", "yes please", "go ahead"})
    state.pending = None                           # consume before executing
    if not ok:
        return {"say": "Okay, I have not made any change."}
    key = f"{state.call_id}:{p['tool_call_id']}"   # idempotency key
    result = execute_once(key, p["tool"], state.verified_account, p["args"])
    return {"tool_output": result, "say": f"Done. Expect a callback in the {p['args']['window']}."}

For higher value actions accept only the keypad. For the highest, do not offer them by voice at all.

Step 6: redact before anything is stored

# redact.py : pattern redaction for transcripts and tool arguments
import re
PATTERNS = [
    (re.compile(r"\b(?:\d[ -]?){13,19}\b"), "[CARD]"),
    (re.compile(r"\b\d{3}-?\d{2}-?\d{4}\b"), "[SSN]"),
    (re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+"), "[EMAIL]"),
    (re.compile(r"\+?\d[\d ()-]{8,}\d"), "[PHONE]"),
]
def redact(text: str) -> str:
    for rx, label in PATTERNS:
        text = rx.sub(label, text)
    return text

Test it on how your recognizer actually writes speech. Numbers often come out as words ("four one one one") or odd groupings, so build test cases from consented recordings, not typed examples.

Step 7: write one log record per turn

{
  "call_id": "rtc_example",
  "turn": 7,
  "ts": "2026-10-25T14:03:12.481Z",
  "caller_verified": true,
  "consent": { "ai_disclosed": true, "recording_announced": true },
  "user_text_redacted": "can you call me back in the afternoon",
  "assistant_text_played_redacted": "I can request a callback in the afternoon. Shall I go ahead?",
  "assistant_truncated_at_ms": null,
  "tool_call": { "name": "request_callback", "args_shape": { "window": "enum" }, "decision": "needs_confirmation" },
  "policy": ["arg_pattern_ok", "verified_caller_ok"],
  "latency_ms": { "end_of_speech_to_first_audio": 640 },
  "handoff": false
}

Log what was played, not what was generated. That is why truncation matters.

Step 8: wire the human handoff and the kill switch

OpenAI's GPT-Live SIP guide documents a refer endpoint that transfers a call to a SIP URI. Whatever your stack, the caller must be able to ask for a person in plain words at any time, and your team must be able to switch the agent off and route every call to people with one configuration change. Test both before launch.

Step 9: test with recordings and a simulated caller

OpenAI's voice agents guide suggests "Crawl, Walk, and Run": synthetic speech for single turns, replayed human recordings, then an independent simulated caller for multi turn calls with interruptions. Add security cases at each stage: a caller asking for another customer's data, a caller trying to rewrite the agent's instructions, a tool result containing instructions, a confirmation interrupted halfway, a duplicate webhook, a call dropped mid action. Check the final application state, not just the words; the guide makes the same point about verifying "that spoken confirmations match completed actions."

Common mistakes

  • Trusting caller ID. SIP From headers are routing hints, and even full STIR/SHAKEN attestation does not identify who holds the phone.
  • Letting voice commands trigger irreversible actions. If a spoken "yes" can move money or change an address, one injected or misheard word can too. Keep writes few, confirm in code, and use the keypad or app for high value actions.
  • Keeping raw audio forever. Set retention in days, not years, and delete on schedule.
  • No human handoff. A trapped caller tries ever more creative phrasings, which is the behaviour you least want near a tool set.
  • Keys and tools in the client. Clients get short lived session secrets; tools run on the server.
  • Ignoring truncation and duplicates. Untruncated history misstates what the caller heard, and retried events double execute without idempotency keys.
  • Redacting after storage. If redaction is an analytics job, the unredacted data was stored.
  • Skipping the AI and recording disclosure. It is one sentence, and regulators in several jurisdictions expect it.

FAQ

What is a voice AI agent?

Software that holds a spoken conversation, uses a language model to decide what to do, can call tools such as lookups or bookings, and replies in synthesized speech. It is built as a cascaded speech to text, model and text to speech pipeline, or around one realtime speech to speech model.

What is the difference between a cascaded voice pipeline and speech to speech?

A cascaded pipeline converts speech to text, runs a text agent and converts the reply to speech, so every stage is inspectable but slower. A speech to speech model answers audio with audio in one session, which is faster and keeps tone, but transcripts arrive alongside the audio rather than as a blocking checkpoint.

Does the OpenAI Realtime API use WebRTC or WebSockets?

Both, plus SIP. OpenAI's docs recommend WebRTC for browser and mobile clients, WebSocket for server to server audio, and SIP for phone calls, with a sideband WebSocket that lets your server attach to a WebRTC or SIP session to run tools.

How does a voice agent know when I have stopped talking?

Voice activity detection and endpointing. Silence based detection ends a turn after a set silence (500 ms in OpenAI's documented example). Semantic detection estimates whether you have finished your thought and waits longer when speech trails off.

Can you prompt inject a voice assistant by talking to it?

Yes. Spoken words become model input like typed text, and OWASP lists prompt injection as LLM01, with extra risk in multimodal systems. The defence is limiting what any instruction can cause: few tools, server side authorization and confirmation for every change.

Is caller ID enough to verify a caller to an AI phone agent?

No. Caller ID can be spoofed, and STIR/SHAKEN attestation says a provider vouches for the number, not who is speaking. Use out of band verification such as an app code entered on the keypad or a callback to the number on file.

Is it legal to use an AI voice for outbound calls in the US?

The FCC's February 2024 ruling FCC 24-17 confirmed that AI generated voices are "artificial" voices under the TCPA, so such calls need the called party's prior express consent unless an emergency purpose or exemption applies. Get legal advice for your use.

Do I need consent to record calls handled by an AI agent?

US federal law permits recording with one party's consent, but some states require all parties, and other countries have their own rules. Announce recording and AI involvement at the start of every call and log it.

How long can a realtime voice session last?

OpenAI's conversation guide states a Realtime session can last at most 60 minutes. Set a shorter cap and save follow up state on your server.

Where Safeguard fits

Safeguard does not listen to calls, verify callers, redact transcripts or enforce confirmations inside your voice agent; those controls live in your server code and telephony setup. The overlap is the software you ship. A voice agent is a web server, a webhook handler, a SIP or media bridge, a provider SDK, WebSocket and audio libraries and a container image, each with transitive dependencies. Safeguard can scan those repositories and images (safeguard scan --dir ., safeguard scan --image <name>), match components against vulnerability databases, apply reachability analysis, show which projects carry a given package version, and gate builds with policies. Autonomous remediation can open fix pull requests and ships switched off by default. If your agents reach tools through MCP, Safeguard's MCP server lets assistants such as Claude query risk with the tools you choose; MCP Guardrail and the AI Gateway runtime guardrail proxy are generally available.

A voice agent stack pulls in a long tail of open source, from WebSocket and HTTP libraries to codecs, SIP bridges, provider SDKs and base images, and those components get vulnerabilities and fixes like any other code. The fit is indirect, since none of this secures a phone call itself, but keeping the code that handles callers' audio and data patched is ordinary supply chain hygiene, and the fork and patch flow below is generally available.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Griffin, Safeguard's security model, opens a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

None of these demos shows a voice agent. Each shows a Safeguard surface that follows the rules this post recommends for voice tools: a named tool set, approval before action, and a person deciding at the point of risk. You can download Safeguard and follow the CLI docs to try them.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. It is the allow list from step 1, set by a person rather than by the model, and the same discipline applies to the tools a voice session declares.

Click through it at your own pace, or open it full screen.

This is a real CLI session: asked to fix a lodash CVE, the CLI reads the finding and advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing. It is the confirm before change pattern from step 5, applied to the dependencies of a service such as a voice webhook handler.

Click through it at your own pace, or open it full screen.

This real Android session sends work to a computer running the CLI and approves the command on the phone with Allow once, Allow for turn or Deny. It is approval on a second device, the same shape as confirming a voice agent's action in an app rather than by speech alone.

Click through it at your own pace, or open it full screen.

Quick check

A call arrives whose SIP From header matches a customer's number on file, with full attestation. Can the agent skip verification and read out order history?

No. Caller ID is routing metadata, and full STIR/SHAKEN attestation only says the originating provider vouches that its customer may use the number. It does not identify who is speaking. Verify out of band, for example with an app code entered on the keypad.

Over a WebSocket, the caller interrupts four seconds into a 20 second reply that ended by asking for confirmation. What must your client do?

Stop playback at once, note how much audio was played, and send a truncate event for that item with the played duration. Otherwise the record and your logs will say the agent asked for confirmation the caller never heard.

Sources

Last reviewed: 25 October 2026. Realtime voice APIs change often; check the provider's current documentation and your counsel's view of local recording and calling rules before you launch.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.