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.
(Optional) Authenticate the agents 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.
If you’re using a private agent that requires authentication, you’ll need to generate
a signed URL from your server. This section explains how to set this up.
Make sure to add .env.local to your .gitignore file to prevent accidentally committing sensitive credentials to version control.
Never expose your API key in the client-side code. Always keep it secure on the server.
2
Create an API route
Create a new file app/api/get-signed-url/route.ts:
app/api/get-signed-url/route.ts
Copy
Ask AI
import { NextResponse } from 'next/server';export async function GET() { try { const response = await fetch( `https://api.elevenlabs.io/v1/convai/conversation/get_signed_url?agent_id=${process.env.NEXT_PUBLIC_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(); return NextResponse.json({ signedUrl: data.signed_url }); } catch (error) { return NextResponse.json( { error: 'Failed to generate signed URL' }, { status: 500 } ); }}
3
Update the Conversation component
Modify your conversation.tsx to fetch and use the signed URL:
app/components/conversation.tsx
Copy
Ask AI
// ... existing imports ...export function Conversation() { // ... existing conversation setup ... const getSignedUrl = async (): Promise<string> => { const response = await fetch("/api/get-signed-url"); if (!response.ok) { throw new Error(`Failed to get signed url: ${response.statusText}`); } const { signedUrl } = await response.json(); return signedUrl; }; const startConversation = useCallback(async () => { try { // Request microphone permission await navigator.mediaDevices.getUserMedia({ audio: true }); const signedUrl = await getSignedUrl(); // Start the conversation with your signed url await conversation.startSession({ signedUrl, }); } catch (error) { console.error('Failed to start conversation:', error); } }, [conversation]); // ... rest of the component ...}
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.