Escolha a API que seu aplicativo usa. Cada API tem seus próprios mecanismos de autenticação e criação de sessões e seu próprio contrato de eventos.
Conecte um navegador ao GPT-Live
Use WebRTC para aplicativos de voz no navegador. A entrada do microfone e a fala gerada trafegam por faixas de mídia negociadas. Um canal de dados transporta eventos JSON de transcrições, atualizações de sessão e trabalho delegado.
Seu navegador cria uma oferta do Session Description Protocol (SDP). O servidor do seu aplicativo a troca por uma resposta usando POST /v1/live/sessions e a chave de API do projeto. Mantenha a chave e a configuração da sessão no seu servidor confiável.
Antes de começar
Você precisa de:
- Uma chave de API do projeto com acesso ao GPT-Live.
- Um ambiente de execução de servidor para o exemplo do SDK escolhido. O exemplo em Node.js exige Node.js 22.6 ou posterior.
- Um navegador com permissão de acesso ao microfone, executando o aplicativo em HTTPS ou localhost.
O exemplo usa delegação para Responses com gpt-5.6-terra e pesquisa na Web hospedada. Para instruções de backend e ferramentas do aplicativo, consulte Delegação e ferramentas. Para informações sobre o uso de voz e backend, consulte Otimização de custos.
Entenda a sequência de conexão
- Solicite acesso ao microfone a partir de uma ação do usuário e adicione suas faixas a uma conexão entre pares.
- Crie o canal de dados e registre os ouvintes de eventos antes de criar a oferta SDP.
- Defina a descrição local, aguarde a coleta de candidatos ICE e envie a oferta ao seu servidor.
- Faça seu servidor enviar à OpenAI, via POST, um JSON contendo
sessionetransport: { type: "webrtc", sdp: ... }. - Aplique a resposta SDP retornada como descrição remota. Aguarde
session.startedno canal de dados antes de enviar comandos do aplicativo.
A requisição HTTP inicia a sessão. Não envie session.start pelo canal de dados. A string oai-events no exemplo é o rótulo do canal de dados.
A criação de uma sessão WebRTC com POST /v1/live/sessions gera uma cobrança de 15 segundos de duração de voz durante a inicialização. Esse valor é abatido das cobranças por duração quando a sessão começa a funcionar; não são 15 segundos extras adicionados à sessão em execução. Consulte Cobranças de inicialização do WebRTC para entender o cálculo dos custos.
Crie o servidor do aplicativo
Salve o exemplo de servidor em um novo diretório e defina OPENAI_API_KEY no ambiente dele. Para Node.js, use server.mjs e instale openai e express com npm install openai express. Para Python, instale openai. Este exemplo escuta em 127.0.0.1, aceita requisições de sessão de http://localhost:3000 e serve index.html a partir do diretório em que você o executa.
Escolha abaixo uma linguagem para o servidor; cada variante serve index.html e o mesmo endpoint /api/session na porta 3000. Use uma versão do SDK com suporte ao Live. Execute apenas uma variante por vez.
import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");
app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
response.type("html").send(await readFile(indexPath, "utf8"));
});
// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
if (request.headers.origin !== origin) {
response.status(403).json({ error: "Unexpected request origin" });
return;
}
if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
response.status(400).json({ error: "An SDP offer is required" });
return;
}
if (!process.env.OPENAI_API_KEY) {
response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
return;
}
try {
const result = await client.live.create({
session: {
model: "gpt-live-1",
instructions:
"Be concise. Delegate requests needing current information to the backend, which can search the web.",
delegation: {
type: "responses",
responses: {
model: "gpt-5.6-terra",
instructions:
"Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
tools: [{ type: "web_search" }],
tool_choice: "auto",
},
},
},
transport: {
type: "webrtc",
sdp: request.body.sdp,
},
});
// Preserve the SDK's typed session ID and SDP answer.
response.status(201).json(result);
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.error("Live session creation failed", error.status);
response
.status(error.status ?? 502)
.json({ error: "Live session creation failed" });
}
});
app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));Antes de disponibilizar o servidor para outros usuários, proteja /api/session com autenticação, autorização, limites de requisições e HTTPS do seu aplicativo. A verificação de origem neste exemplo local não autentica usuários.
Crie o cliente do navegador
Crie index.html no diretório em que você executa o servidor:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GPT-Live connection</title>
</head>
<body>
<script type="module">
// Paste the browser code below here.
</script>
</body>
</html>Cole o código a seguir dentro do script de módulo. Ele adiciona controles para iniciar e encerrar, conecta o microfone e a saída de áudio e trata os eventos da sessão. /api/session é uma rota no servidor do seu aplicativo.
const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);
let peer;
let events;
let microphone;
let closeTimeout;
let ready = false;
let finalized = false;
function cleanup() {
clearTimeout(closeTimeout);
microphone?.getTracks().forEach((track) => track.stop());
events?.close();
peer?.close();
audio.srcObject = null;
ready = false;
start.disabled = false;
stop.disabled = true;
}
start.addEventListener("click", async () => {
start.disabled = true;
finalized = false;
status.textContent = "Connecting…";
try {
const connection = new RTCPeerConnection();
peer = connection;
connection.addEventListener("track", (event) => {
audio.srcObject = new MediaStream([event.track]);
audio.play().catch(() => {
status.textContent =
"Select play on the audio controls to hear the assistant.";
});
});
microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
for (const track of microphone.getAudioTracks()) {
connection.addTrack(track, microphone);
}
// Create the event channel before creating the SDP offer.
events = connection.createDataChannel("oai-events");
events.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
if (event.type === "session.started") {
ready = true;
stop.disabled = false;
status.textContent = "Connected: " + event.session.id;
} else if (event.type === "session.closed") {
finalized = true;
console.log("Final session usage", event.usage);
status.textContent = "Conversation ended.";
cleanup();
} else {
// Save transcript and nested Responses events as needed by your app.
console.log(event);
}
});
events.addEventListener("close", (event) => {
if (event.target !== events) return;
if (!finalized) {
status.textContent = "Disconnected without final session usage.";
cleanup();
}
});
const offer = await connection.createOffer();
await connection.setLocalDescription(offer);
if (connection.iceGatheringState !== "complete") {
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
connection.removeEventListener("icegatheringstatechange", onState);
reject(new Error("Timed out while gathering ICE candidates"));
}, 10_000);
function onState() {
if (connection.iceGatheringState !== "complete") return;
clearTimeout(timeout);
connection.removeEventListener("icegatheringstatechange", onState);
resolve(undefined);
}
connection.addEventListener("icegatheringstatechange", onState);
onState();
});
}
const sdp = connection.localDescription?.sdp;
if (!sdp) throw new Error("Missing local SDP offer");
const response = await fetch("/api/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sdp }),
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
console.log("Created session", result.session.id);
await connection.setRemoteDescription({
type: "answer",
sdp: result.transport.sdp,
});
// The HTTP request started this session. Do not send session.start here.
} catch (error) {
status.textContent =
error instanceof Error ? error.message : String(error);
cleanup();
}
});
stop.addEventListener("click", () => {
if (!ready || !events || events.readyState !== "open") return;
stop.disabled = true;
status.textContent = "Finishing the conversation…";
// The session.closed handler is already registered. Keep media and events
// alive while pending work drains; only clean up after the final event.
events.send(JSON.stringify({ type: "session.close" }));
closeTimeout = setTimeout(() => {
status.textContent = "Incomplete finalization: no session.closed event.";
cleanup();
}, 15_000);
});Execute o servidor escolhido (node server.mjs ou python server.py), abra http://localhost:3000 e selecione Iniciar conversa. Depois que o status mudar para Conectado, faça uma pergunta que exija informações atuais para testar a pesquisa hospedada. Use os controles de áudio se o navegador bloquear a reprodução automática.
Leia a resposta da sessão
Uma requisição bem-sucedida retorna HTTP 201 com um JSON contendo o ID da sessão e a resposta SDP:
{
"session": { "id": "live_123" },
"transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}Leia result.session.id e passe result.transport.sdp para setRemoteDescription. Trate o ID da sessão como opaco e preserve-o sem alterações, incluindo seu prefixo.
Trate mídia e eventos
Envie o áudio do microfone e receba a fala gerada pelas faixas de mídia. O WebRTC negocia o formato de áudio por meio do SDP, portanto omita audio.format da configuração da sessão. Não envie session.input_audio.append nem espere receber session.output_audio.delta pelo canal de dados.
Use o canal de dados para deltas de transcrição, comandos de sessão e mensagens response.event aninhadas. Consulte Gerenciamento de sessões para saber como tratar transcrições e eventos do ciclo de vida, e Controles do lado do servidor se o seu servidor precisar de uma conexão própria para eventos.
Para encerrar a conversa, envie session.close e continue recebendo eventos até chegar session.closed, antes de fechar a conexão entre pares e encerrar as faixas do microfone. O exemplo registra o ouvinte do evento final antes de enviar o comando. Se a conexão falhar ou atingir o tempo limite antes disso, o uso final ficará sem confirmação. Consulte Uso e encerramento adequado para saber como tratar o uso final.
O WebRTC é um conjunto poderoso de interfaces padrão para criar aplicativos em tempo real. A Realtime API da OpenAI permite a conexão com modelos em tempo real por meio de uma conexão entre pares WebRTC.
Para aplicativos de voz de fala para fala no navegador, recomendamos começar por Agentes de voz, que aborda as funções auxiliares e APIs de mais alto nível do SDK de Agentes para gerenciar sessões em tempo real. A interface WebRTC é poderosa e flexível, mas tem um nível de abstração mais baixo que o SDK de Agentes.
Ao se conectar a um modelo Realtime a partir do cliente (como um navegador ou dispositivo móvel), recomendamos usar WebRTC em vez de WebSockets para obter um desempenho mais consistente.
Para mais orientações sobre como criar interfaces de usuário com WebRTC, consulte a documentação na MDN.
Visão geral
A Realtime API oferece dois mecanismos para se conectar a ela a partir do navegador: usando chaves de API efêmeras (geradas pela API REST da OpenAI) ou a nova interface unificada. Em geral, usar a interface unificada é mais simples, mas coloca o servidor do seu aplicativo no caminho crítico da inicialização da sessão.
Conexão usando a interface unificada
O processo para inicializar uma conexão WebRTC usando a interface unificada é o seguinte (considerando um cliente de navegador):
- O navegador faz uma requisição a um servidor controlado pelo desenvolvedor usando os dados SDP da sua conexão entre pares WebRTC.
- O servidor combina esse SDP com a configuração da sessão em um formulário multipart e o envia à Realtime API da OpenAI, autenticando a requisição com sua chave de API padrão.
Criação de uma sessão pela interface unificada
Para criar uma sessão da Realtime API pela interface unificada, você precisará desenvolver um pequeno aplicativo do lado do servidor (ou integrar essa funcionalidade a um existente) para fazer uma requisição a /v1/realtime/calls. Você usará uma chave de API padrão para autenticar essa requisição no seu servidor de backend.
Veja abaixo um exemplo de servidor simples em Node.js com express que cria uma sessão da Realtime API:
import express from "express";
const app = express();
// Parse raw SDP payloads posted from the browser
app.use(express.text({ type: ["application/sdp", "text/plain"] }));
const sessionConfig = JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
audio: { output: { voice: "marin" } },
});
// An endpoint which creates a Realtime API session.
app.post("/session", async (req, res) => {
const fd = new FormData();
fd.set("sdp", req.body);
fd.set("session", sessionConfig);
try {
const r = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: fd,
});
// Send back the SDP we received from the OpenAI REST API
const sdp = await r.text();
res.send(sdp);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Se o seu aplicativo atribui um identificador de segurança
a cada usuário final, inclua-o no cabeçalho OpenAI-Safety-Identifier desta
requisição do lado do servidor. Use um valor estável que preserve a privacidade, como um hash
do ID interno do usuário. O cabeçalho deve ser definido pelo seu backend confiável, não pelo
navegador.
Conexão com o servidor
No navegador, você pode usar as APIs padrão do WebRTC para se conectar à Realtime API por meio do servidor do seu aplicativo. O cliente envia seus dados SDP diretamente ao seu servidor via POST.
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("/session", {
method: "POST",
body: offer.sdp,
headers: {
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Conexão usando um token efêmero
O processo para inicializar uma conexão WebRTC usando uma chave de API efêmera é o seguinte (considerando um navegador Web como cliente):
- O navegador faz uma solicitação a um servidor controlado pelo desenvolvedor para gerar uma chave de API efêmera.
- O servidor do desenvolvedor usa uma chave de API padrão para solicitar uma chave efêmera à API REST da OpenAI e retorna essa nova chave ao navegador.
- O navegador usa a chave efêmera para autenticar uma sessão diretamente com a Realtime API da OpenAI como uma conexão entre pares WebRTC.
Criação de um token efêmero
Para criar um token efêmero para uso no lado do cliente, você precisará desenvolver uma pequena aplicação no lado do servidor (ou integrar essa funcionalidade a uma aplicação existente) para solicitar uma chave efêmera à API REST da OpenAI. Você usará uma chave de API padrão para autenticar essa solicitação no seu servidor de backend.
Veja abaixo um exemplo de servidor simples em Node.js com express que gera uma chave de API efêmera usando a API REST:
import express from "express";
const app = express();
const sessionConfig = JSON.stringify({
session: {
type: "realtime",
model: "gpt-realtime-2.1",
audio: {
output: {
voice: "marin",
},
},
},
});
// An endpoint which would work with the client code above - it returns
// the contents of a REST API request to this protected endpoint
app.get("/token", async (req, res) => {
try {
const response = await fetch(
"https://api.openai.com/v1/realtime/client_secrets",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: sessionConfig,
}
);
const data = await response.json();
res.json(data);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Você pode criar um endpoint de servidor como este em qualquer plataforma que possa enviar e receber solicitações HTTP. Apenas garanta que as chaves de API padrão da OpenAI sejam usadas somente no servidor, nunca no navegador.
Ao usar tokens efêmeros, defina OpenAI-Safety-Identifier na solicitação feita pelo servidor
que cria o segredo do cliente. A Realtime API vincula o identificador ao
token efêmero resultante, de modo que o navegador não precisa enviar o identificador de segurança
ao se conectar posteriormente com esse token.
Conexão com o servidor
No navegador, você pode usar as APIs WebRTC padrão para se conectar à Realtime API com um token efêmero. Primeiro, o cliente obtém um token do endpoint do seu servidor e, em seguida, envia seus dados SDP (com o token efêmero) à Realtime API em uma solicitação POST.
// Get a session token for OpenAI Realtime API
const tokenResponse = await fetch("/token");
const data = await tokenResponse.json();
const EPHEMERAL_KEY = data.value;
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);
// Set up data channel for sending and receiving events
const dc = pc.createDataChannel("oai-events");
// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
});
const answer = {
type: "answer",
sdp: await sdpResponse.text(),
};
await pc.setRemoteDescription(answer);Envio e recebimento de eventos
As sessões da Realtime API são gerenciadas por uma combinação de eventos enviados pelo cliente, emitidos por você como desenvolvedor, e eventos enviados pelo servidor, criados pela Realtime API para indicar eventos do ciclo de vida da sessão.
Ao se conectar a um modelo Realtime via WebRTC, você não precisa tratar os eventos de áudio do modelo com o mesmo nível de detalhe exigido com WebSockets. O objeto de conexão entre pares WebRTC, se configurado conforme mostrado acima, fará todo esse trabalho por você.
Para enviar e receber outros eventos do cliente e do servidor, você pode usar o canal de dados da conexão entre pares WebRTC.
// This is the data channel set up in the browser code above...
const dc = pc.createDataChannel("oai-events");
// Listen for server events
dc.addEventListener("message", (e) => {
const event = JSON.parse(e.data);
console.log(event);
});
// Send client events
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "hello there!",
},
],
},
};
dc.send(JSON.stringify(event));Para saber mais sobre como gerenciar conversas Realtime, consulte o guia de conversas Realtime.
Confira a Realtime API com WebRTC neste aplicativo de exemplo leve.