Learn how to create a web application that enables voice conversations with ElevenLabs AI agents
This tutorial will guide you through creating a web client that can interact with a Conversational AI agent. You’ll learn how to implement real-time voice conversations, allowing users to speak with an AI agent that can listen, understand, and respond naturally using voice synthesis.
Looking to build with React/Next.js? Check out our Next.js
guide.
Make sure to replace 'YOUR_AGENT_ID' with your actual agent ID from
ElevenLabs.
(Optional) Authenticate with a Signed URL
This authentication step is only required for private agents. If you’re using a public agent, you can skip this section and directly use the agentId in the startSession call.
Make sure to add .env to your .gitignore file to prevent accidentally committing sensitive credentials.
2
Setup the Backend
Install additional dependencies:
Copy
Ask AI
npm install express cors dotenv
Create a new folder called backend:
Copy
Ask AI
elevenlabs-conversational-ai/├── backend...
3
Create the Server
backend/server.js
Copy
Ask AI
require("dotenv").config();const express = require("express");const cors = require("cors");const app = express();app.use(cors());app.use(express.json());const PORT = process.env.PORT || 3001;app.get("/api/get-signed-url", async (req, res) => { try { const response = await fetch( `https://api.elevenlabs.io/v1/convai/conversation/get_signed_url?agent_id=${process.env.AGENT_ID}`, { headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY, }, } ); if (!response.ok) { throw new Error("Failed to get signed URL"); } const data = await response.json(); res.json({ signedUrl: data.signed_url }); } catch (error) { console.error("Error:", error); res.status(500).json({ error: "Failed to generate signed URL" }); }});app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`);});
4
Update the Client Code
Modify your script.js to fetch and use the signed URL:
script.js
Copy
Ask AI
// ... existing imports and variables ...async function getSignedUrl() { const response = await fetch('http://localhost:3001/api/get-signed-url'); if (!response.ok) { throw new Error(`Failed to get signed url: ${response.statusText}`); } const { signedUrl } = await response.json(); return signedUrl;}async function startConversation() { try { await navigator.mediaDevices.getUserMedia({ audio: true }); const signedUrl = await getSignedUrl(); conversation = await Conversation.startSession({ signedUrl, // agentId has been removed... onConnect: () => { connectionStatus.textContent = 'Connected'; startButton.disabled = true; stopButton.disabled = false; }, onDisconnect: () => { connectionStatus.textContent = 'Disconnected'; startButton.disabled = false; stopButton.disabled = true; }, onError: (error) => { console.error('Error:', error); }, onModeChange: (mode) => { agentStatus.textContent = mode.mode === 'speaking' ? 'speaking' : 'listening'; }, }); } catch (error) { console.error('Failed to start conversation:', error); }}// ... rest of the code ...
Signed URLs expire after a short period. However, any conversations initiated before expiration will continue uninterrupted. In a production environment, implement proper error handling and URL refresh logic for starting new conversations.
5
Update the package.json
package.json
Copy
Ask AI
{ "scripts": { ... "dev:backend": "node backend/server.js", "dev": "npm run dev:frontend & npm run dev:backend" }}