Choisissez votre API pour consulter les étapes de connexion et les événements de session correspondants.
Connectez un navigateur à GPT-Live
Nous recommandons WebRTC pour les connexions audio à GPT-Live depuis un navigateur, une application mobile ou une application côté serveur. Ce guide présente un exemple dans un navigateur. Le son du microphone et la parole générée transitent sur les pistes multimédias négociées par WebRTC. Un canal de données transporte les événements JSON liés aux transcriptions, aux mises à jour de la session et aux tâches déléguées.
Votre navigateur crée une offre SDP (Session Description Protocol). Votre serveur d’application envoie cette offre avec POST /v1/live/sessions en utilisant la clé API du projet et reçoit une réponse SDP. Conservez la clé et la configuration de la session sur votre serveur de confiance.
Prérequis
Vous aurez besoin des éléments suivants :
- Une clé API de projet donnant accès à GPT-Live.
- Un environnement d’exécution côté serveur adapté à l’exemple de SDK choisi. L’exemple Node.js nécessite Node.js 22.6 ou une version ultérieure.
- Un navigateur autorisé à utiliser le microphone et dans lequel l’application s’ouvre en HTTPS ou sur localhost.
L’exemple utilise la délégation via Responses avec gpt-5.6-terra et la recherche web hébergée. Pour les instructions côté serveur et les outils de l’application, consultez Délégation et outils. Pour en savoir plus sur l’utilisation des services vocaux et du backend, consultez Optimisation des coûts.
Déroulement de la connexion
- En réponse à une action de l’utilisateur, demandez l’accès au microphone et ajoutez ses pistes à une connexion pair à pair.
- Créez le canal de données et enregistrez les écouteurs d’événements avant de créer l’offre SDP.
- Définissez la description locale, attendez la fin de la collecte des candidats ICE, puis envoyez l’offre à votre serveur.
- Depuis votre serveur, envoyez à OpenAI une requête POST dont le corps JSON contient
sessionettransport: { type: "webrtc", sdp: ... }. - Utilisez la réponse SDP reçue comme description distante. Attendez de recevoir
session.startedsur le canal de données avant d’envoyer des commandes de l’application.
La requête HTTP démarre la session. N’envoyez pas session.start sur le canal de données. Dans l’exemple, la chaîne oai-events est le libellé du canal de données.
Lorsque vous créez une session WebRTC avec POST /v1/live/sessions, 15 secondes d’utilisation vocale sont facturées à l’initialisation. Une fois la session démarrée, ce montant est déduit des frais liés à sa durée. Ces 15 secondes ne s’ajoutent donc pas à la durée facturée. Pour le détail du calcul, consultez Frais d’initialisation de WebRTC.
Créez le serveur d’application
Enregistrez l’exemple de serveur dans un nouveau répertoire et définissez OPENAI_API_KEY dans l’environnement du serveur. Pour Node.js, utilisez le fichier server.mjs et installez openai et express avec npm install openai express. Pour Python, installez openai. Dans cet exemple, le serveur écoute sur 127.0.0.1, accepte les demandes de session provenant de http://localhost:3000 et sert le fichier index.html depuis le répertoire où vous le lancez.
Pour Go, enregistrez l’exemple sous le nom main.go, initialisez un module avec go mod init live-demo et installez github.com/openai/openai-go/v3@v3.85.1 avec go get. Exécutez go run . avec Go 1.26 ou une version ultérieure. Pour Java, utilisez Java 17 ou une version ultérieure, ajoutez la dépendance Maven com.openai:openai-java:4.95.0 et exécutez la classe LiveConnectionWebrtcExample. Pour Ruby 3.3 ou une version ultérieure, installez openai en version 0.110.0 et webrick avec gem install openai -v 0.110.0 et gem install webrick, enregistrez l’exemple sous le nom server.rb, puis exécutez ruby server.rb.
Choisissez ci-dessous le langage du serveur. Chaque variante sert le fichier index.html et expose le même point de terminaison /api/session sur le port 3000. Utilisez une version du SDK qui prend en charge Live. N’exécutez qu’une variante à la fois.
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}`));Avant de rendre le serveur accessible à d’autres utilisateurs, protégez /api/session en appliquant les mécanismes d’authentification et d’autorisation de votre application, en limitant les requêtes et en utilisant HTTPS. La vérification de l’origine dans cet exemple local n’authentifie pas les utilisateurs.
Créez le client dans le navigateur
Créez le fichier index.html dans le répertoire où vous lancez le serveur :
<!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>Collez le code suivant dans la balise de script du module. Il ajoute les commandes qui permettent de démarrer et de terminer la conversation, connecte le microphone et la sortie audio, et gère les événements de session. /api/session est une route de votre serveur d’application.
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());
const closingEvents = events;
events = undefined;
closingEvents?.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);
});Lancez le serveur choisi (node server.mjs ou python server.py), ouvrez http://localhost:3000 et sélectionnez Start conversation. Lorsque l’état passe à Connected, posez une question qui nécessite des informations à jour pour tester la recherche web hébergée. Utilisez les commandes audio si votre navigateur bloque la lecture automatique.
Lisez la réponse de création de session
Si la requête aboutit, le serveur renvoie le code HTTP 201 et un objet JSON contenant l’identifiant de session et la réponse SDP :
{
"session": { "id": "live_123" },
"transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}Lisez result.session.id et passez result.transport.sdp à setRemoteDescription. Traitez l’identifiant de session comme une valeur opaque et conservez-le tel quel, préfixe compris.
Pour réduire le délai d’établissement de la connexion, consultez WebRTC avec WARP.
Gérez les flux multimédias et les événements
Utilisez les pistes multimédias pour envoyer le son du microphone et recevoir la parole générée. WebRTC négocie le format audio via SDP. Omettez donc audio.format dans la configuration de la session. Sur le canal de données, n’envoyez pas session.input_audio.append et ne vous attendez pas à recevoir session.output_audio.delta.
Utilisez le canal de données pour les mises à jour incrémentales des transcriptions, les commandes de session et les messages response.event imbriqués. Consultez Gestion des sessions pour le traitement des transcriptions et les événements du cycle de vie, et Contrôles côté serveur si votre serveur a besoin de sa propre connexion pour les événements.
Pour terminer la conversation, envoyez session.close et continuez à recevoir les événements jusqu’à session.closed. Fermez ensuite la connexion pair à pair et arrêtez les pistes du microphone. L’exemple enregistre l’écouteur de l’événement final avant d’envoyer la commande. Si la connexion échoue ou si le délai d’attente expire avant la réception de cet événement, les données d’utilisation finales ne sont pas confirmées. Pour savoir comment les traiter, consultez Utilisation et fermeture propre de la session.
WebRTC offre un ensemble puissant d’interfaces standard pour créer des applications en temps réel. La Realtime API d’OpenAI permet de se connecter à des modèles temps réel via une connexion WebRTC pair à pair.
Pour créer une application vocale de parole à parole dans un navigateur, nous vous recommandons de commencer par le guide Agents vocaux. Il présente les fonctions utilitaires et les API de haut niveau du SDK Agents pour gérer les sessions Realtime. L’interface WebRTC est puissante et souple, mais elle se situe à un niveau d’abstraction inférieur à celui du SDK Agents.
Pour vous connecter à un modèle Realtime depuis un client tel qu’un navigateur ou un appareil mobile, privilégiez WebRTC aux WebSockets pour obtenir des performances plus régulières.
Pour en savoir plus sur la création d’interfaces utilisateur avec WebRTC, consultez la documentation MDN.
Vue d’ensemble
La Realtime API propose deux méthodes de connexion depuis un navigateur : l’interface unifiée et les clés API éphémères (générées via l’API REST d’OpenAI). Utilisez l’interface unifiée pour simplifier la configuration et accélérer les connexions. Avec cette méthode, l’initialisation de la session dépend directement de votre serveur d’application.
Connexion via l’interface unifiée
Pour initialiser une connexion WebRTC via l’interface unifiée depuis un navigateur, le client et le serveur procèdent ainsi :
- Le navigateur envoie à un serveur que vous contrôlez une requête contenant les données SDP de sa connexion WebRTC pair à pair.
- Le serveur réunit les données SDP et la configuration de la session dans un formulaire multipart, puis envoie le tout à la Realtime API d’OpenAI. Il authentifie la requête avec sa clé API standard.
Création d’une session via l’interface unifiée
Pour créer une session de la Realtime API via l’interface unifiée, créez une petite application côté serveur ou adaptez une application existante pour envoyer une requête à /v1/realtime/calls. Utilisez une clé API standard sur votre serveur backend pour authentifier cette requête.
Créez une session de la Realtime API depuis votre serveur d’application :
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.set(
"Content-Type",
r.ok ? "application/sdp" : (r.headers.get("Content-Type") ?? "text/plain")
);
const location = r.headers.get("Location");
if (location) res.set("Location", location);
res.status(r.status).send(sdp);
} catch (error) {
console.error("Session creation error:", error);
res.status(500).json({ error: "Failed to create session" });
}
});
app.listen(3000);Si votre application attribue un identifiant de sécurité à chaque utilisateur final, transmettez-le dans l’en-tête OpenAI-Safety-Identifier de cette requête côté serveur. Utilisez une valeur stable qui préserve la confidentialité, par exemple le hachage d’un identifiant utilisateur interne. Définissez cet en-tête sur votre backend de confiance, et non dans le navigateur.
Connexion au serveur
Dans le navigateur, utilisez les API WebRTC standard pour vous connecter à la Realtime API via votre serveur d’application. Le client envoie ses données SDP à votre serveur dans une requête 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);Connexion avec un jeton éphémère
Pour initialiser une connexion WebRTC avec une clé API éphémère depuis un navigateur, le client et le serveur procèdent ainsi :
- Le navigateur demande à un serveur que vous contrôlez de générer une clé API éphémère.
- Votre serveur utilise une clé API standard pour demander une clé éphémère à l’API REST d’OpenAI, puis renvoie cette nouvelle clé au navigateur.
- Le navigateur utilise la clé éphémère pour authentifier la session directement auprès de la Realtime API d’OpenAI via une connexion WebRTC pair à pair.
Création d’un jeton éphémère
Pour créer un jeton éphémère utilisable côté client, créez une petite application côté serveur ou adaptez une application existante pour demander une clé éphémère à l’API REST d’OpenAI. Utilisez une clé API standard sur votre serveur backend pour authentifier cette requête.
Créez une clé API éphémère depuis votre serveur d’application avec l’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) => {
res.set("Cache-Control", "no-store");
try {
const response = await fetch(
"https://api.openai.com/v1/realtime/client_secrets",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
"OpenAI-Safety-Identifier": "hashed-user-id",
},
body: sessionConfig,
}
);
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
console.error("Token generation error:", error);
res.status(500).json({ error: "Failed to generate token" });
}
});
app.listen(3000);Vous pouvez créer un point de terminaison comme celui-ci sur toute plateforme capable d’envoyer et de recevoir des requêtes HTTP. Veillez à utiliser les clés API standard d’OpenAI uniquement sur le serveur, jamais dans le navigateur.
Lorsque vous utilisez des jetons éphémères, définissez OpenAI-Safety-Identifier dans la requête côté serveur qui crée le secret client. La Realtime API associe l’identifiant au jeton éphémère obtenu. Le navigateur n’a donc pas besoin d’envoyer l’identifiant de sécurité lorsqu’il se connecte ensuite avec ce jeton.
Connexion au serveur
Dans le navigateur, utilisez les API WebRTC standard pour vous connecter à la Realtime API avec un jeton éphémère. Le client récupère d’abord un jeton auprès du point de terminaison de votre serveur, puis envoie ses données SDP et le jeton éphémère à la Realtime API dans une requête 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);Réduisez la latence de connexion avec WARP
Le protocole WebRTC Abridged Roundtrip Protocol (WARP) réduit le temps nécessaire au démarrage d’une session vocale de la Realtime API. Vous pouvez activer ses optimisations séparément ou les combiner pour effectuer la négociation WARP complète.
Consultez WebRTC avec WARP pour connaître la configuration des clients natifs, la prise en charge par les navigateurs, la procédure d’activation à titre expérimental pour une origine et les étapes de connexion via l’interface unifiée.
Envoi et réception d’événements
Pour gérer les sessions de la Realtime API, votre application envoie des événements côté client et reçoit des événements côté serveur que la Realtime API génère pour signaler les étapes du cycle de vie de la session.
Lorsque vous vous connectez à un modèle Realtime via WebRTC, vous n’avez pas à traiter les événements audio du modèle aussi finement qu’avec les WebSockets. Si vous le configurez comme indiqué ci-dessus, l’objet de connexion WebRTC pair à pair s’en charge.
Pour envoyer et recevoir les autres événements du client et du serveur, utilisez le canal de données de la connexion WebRTC pair à pair.
// Reuse dc from the preceding browser connection example.
// 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!",
},
],
},
};
const sendGreeting = () => dc.send(JSON.stringify(event));
if (dc.readyState === "open") {
sendGreeting();
} else if (dc.readyState === "connecting") {
dc.addEventListener("open", sendGreeting, { once: true });
} else {
throw new Error("The data channel has closed before greeting was sent.");
}Pour en savoir plus sur la gestion des conversations Realtime, consultez le guide des conversations Realtime.
Explorez la Realtime API avec WebRTC dans cette application d’exemple légère.