Skip to navigation

Gegevens verzamelen en analyseren met Agents Platform in Next.js

Verzamel en analyseer gegevens in post-call webhooks met Agents Platform en Next.js.

Tutorial · Hierbij wordt ervan uitgegaan dat je de ElevenAgents quickstart hebt voltooid en een Next.js-project hebt ingesteld.

Inleiding

In deze tutorial leer je hoe je een spraakagent bouwt die via een gesprek informatie van de gebruiker verzamelt, de gegevens vervolgens op een gestructureerde manier analyseert en extraheert, en deze via de post-call webhook naar je applicatie verstuurt.

Vereisten

  • Een ElevenLabs-account met een API-sleutel.
  • Node.js v18 of hoger geïnstalleerd op je computer.

Instellen

Een nieuw Next.js-project maken

We raden aan om onze v0.dev Agents Platform-template als uitgangspunt voor je applicatie te gebruiken. Deze template is een productieklaar Next.js-applicatie waarin de ElevenLabs-agent al is geïntegreerd.

Agents Platform instellen

Volg onze Next.js-handleiding voor de installatie- en configuratiestappen. Kom daarna hier terug om de geavanceerde functies te bouwen.

Agentconfiguratie

1

Inloggen bij ElevenLabs

Ga naar elevenlabs.io en log in op je account.

2

Een nieuwe agent maken

Ga naar Agents Platform > Agents en maak een nieuwe agent op basis van de lege template.

3

Het eerste bericht instellen

Stel het eerste bericht in en geef de dynamische variabele voor het platform op.

Hi {{user_name}}, I'm Jess from the ElevenLabs team. I'm here to help you design your very own ElevenLabs agent! To kick things off, let me know what kind of agent you're looking to create. For example, do you want a support agent, to help your users answer questions, or a sales agent to sell your products, or just a friend to chat with?
4

De systeemprompt instellen

Stel de systeemprompt in. Je kunt hier ook dynamische variabelen opnemen.

You are Jess, a helpful agent helping {{user_name}} to design their very own ElevenLabs agent. The design process involves the following steps:
"initial": In the first step, collect the information about the kind of agent the user is looking to create. Summarize the user's needs back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"training": Tell the user to create the agent's knowledge base by uploading documents, or submitting URLs to public websites with information that should be available to the agent. Wait patiently without talking to the user. Only when the user confirms that they've provided everything then proceed to the next step.
"voice": Tell the user to describe the voice they want their agent to have. For example: "A professional, strong spoken female voice with a slight British accent." Repeat the description of their voice back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"email": Tell the user that we've collected all necessary information to create their ElevenLabs agent and ask them to provide their email address to get notified when the agent is ready.
Always call the `set_ui_state` tool when moving between steps!
5

De clienttools instellen

Stel de volgende clienttool in om tussen de stappen te navigeren:

  • Naam: set_ui_state
    • Beschrijving: Gebruik deze client-side tool om tussen de verschillende UI-statussen te navigeren.
    • Wachten op antwoord: true
    • Responstime-out (seconden): 1
    • Parameters:
      • Gegevenstype: string
      • ID: step
      • Vereist: true
      • Waardetype: LLM Prompt
      • Beschrijving: De stap waarnaar je in de UI wilt navigeren. Gebruik alleen de stappen die in de systeemprompt zijn gedefinieerd!
6

De stem van je agent instellen

Ga naar het tabblad Voice en stel de stem voor je agent in. In de documentatie over Conversational Voice Design vind je een lijst met aanbevolen stemmen voor Agents Platform.

7

De evaluatiecriteria instellen

Ga naar het tabblad Analysis en voeg een nieuw evaluatiecriterium toe.

  • Naam: all_data_provided
    • Prompt: Beoordeel of de gebruiker een beschrijving heeft gegeven van de agent die deze wil genereren, evenals een beschrijving van de stem die de agent moet hebben.
8

De gegevensverzameling configureren

Je kunt de analyse na het gesprek gebruiken om gegevens uit het gesprek te extraheren. Voeg op het tabblad Analysis, onder Data Collection, de volgende items toe:

  • ID: voice_description
    • data-type: String
    • Beschrijving: Genereer op basis van de beschrijving van de stem die de gebruiker voor de agent wil, een beknopte beschrijving van de stem met, indien beschikbaar, de leeftijd, het accent, de toon en het karakter.
  • ID: agent_description
    • data-type: String
    • Beschrijving: Genereer op basis van de beschrijving van de agent die de gebruiker wil ontwerpen een prompt waarmee een model kan worden getraind om als de agent te handelen.
9

De post-call webhook configureren

Post-call webhooks worden gebruikt om je te informeren wanneer een gesprek eindigt en de stappen voor analyse en gegevensextractie zijn voltooid.

In dit voorbeeld voert de post-call webhook enkele stappen uit, namelijk:

  1. Een aangepast stemontwerp maken op basis van de voice_description.
  2. Een ElevenLabs-agent voor de gebruikers maken op basis van de agent_description die zij hebben opgegeven.
  3. De kennisbankdocumenten ophalen uit de gespreksstatus die in Redis is opgeslagen en de kennisbank aan de agent koppelen.
  4. De gebruiker een e-mail sturen om te melden dat de aangepaste ElevenLabs-agent klaar is om te chatten.

Wanneer je lokaal werkt, heb je een tool zoals ngrok nodig om je lokale server toegankelijk te maken via internet.

ngrok http 3000

Ga naar de instellingen van Agents Platform en maak onder Post-Call Webhook een nieuwe webhook. Plak daar je ngrok-URL: https://<your-url>.ngrok-free.app/api/convai-webhook.

Nadat je de webhook hebt opgeslagen, ontvang je een webhooksecret. Bewaar dit secret veilig, want je moet het later in je bestand .env instellen.

De geavanceerde functies integreren

Een Redis-database instellen om de gespreksstatus op te slaan

In dit voorbeeld gebruiken we Redis om de gespreksstatus op te slaan. Hierdoor kunnen we de kennisbankdocumenten na afloop van het gesprek uit de gespreksstatus ophalen.

Als je implementeert op Vercel, kun je de integratie Upstash for Redis configureren. Je kunt ook een gratis Upstash-account aanmaken en een nieuwe database maken.

Resend instellen voor het versturen van e-mails na gesprekken

In dit voorbeeld gebruiken we Resend om de e-mail na het gesprek naar de gebruiker te sturen. Hiervoor moet je een gratis Resend-account maken en een nieuwe API-sleutel instellen.

De omgevingsvariabelen instellen

Maak in de hoofdmap van je project een bestand .env en voeg de volgende variabelen toe:

ELEVENLABS_CONVAI_WEBHOOK_SECRET=
ELEVENLABS_API_KEY=
ELEVENLABS_AGENT_ID=
# Resend
RESEND_API_KEY=
RESEND_FROM_EMAIL=
# Upstash Redis
KV_URL=
KV_REST_API_READ_ONLY_TOKEN=
REDIS_URL=
KV_REST_API_TOKEN=
KV_REST_API_URL=

Beveiliging en authenticatie configureren

Om je ElevenLabs-agent te beveiligen, moet je authenticatie inschakelen op het tabblad Security van de agentconfiguratie.

Zodra authenticatie is ingeschakeld, moet je in een beveiligde server-side omgeving een ondertekende URL maken om een gesprek met de agent te starten. In Next.js kun je dit doen door een nieuwe API-route in te stellen.

./app/api/signed-url/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { NextResponse } from "next/server";
export async function GET() {
const agentId = process.env.ELEVENLABS_AGENT_ID;
if (!agentId) {
throw Error("ELEVENLABS_AGENT_ID is not set");
}
try {
const elevenlabs = new ElevenLabsClient();
const response = await elevenlabs.conversationalAi.conversations.getSignedUrl({
agentId,
});
return NextResponse.json({ signedUrl: response.signedUrl });
} catch (error) {
console.error("Error:", error);
return NextResponse.json({ error: "Failed to get signed URL" }, { status: 500 });
}
}

De gesprekssessie starten

Om het gesprek te starten, roep je eerst je API-route aan om de ondertekende URL op te halen. Gebruik vervolgens de hook useConversation om de gesprekssessie in te stellen.

./page.tsx
import { useConversation } from "@elevenlabs/react";
async function getSignedUrl(): Promise<string> {
const response = await fetch("/api/signed-url");
if (!response.ok) {
throw Error("Failed to get signed url");
}
const data = await response.json();
return data.signedUrl;
}
export default function Home() {
// ...
const [currentStep, setCurrentStep] = useState<
"initial" | "training" | "voice" | "email" | "ready"
>("initial");
const [conversationId, setConversationId] = useState("");
const [userName, setUserName] = useState("");
const conversation = useConversation({
onConnect: () => console.log("Connected"),
onDisconnect: () => console.log("Disconnected"),
onMessage: (message: string) => console.log("Message:", message),
onError: (error: Error) => console.error("Error:", error),
});
const startConversation = useCallback(async () => {
try {
// Request microphone permission
await navigator.mediaDevices.getUserMedia({ audio: true });
// Start the conversation with your agent
const signedUrl = await getSignedUrl();
const convId = await conversation.startSession({
signedUrl,
dynamicVariables: {
user_name: userName,
},
clientTools: {
set_ui_state: ({ step }: { step: string }): string => {
// Allow agent to navigate the UI.
setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
return `Navigated to ${step}`;
},
},
});
setConversationId(convId);
console.log("Conversation ID:", convId);
} catch (error) {
console.error("Failed to start conversation:", error);
}
}, [conversation, userName]);
const stopConversation = useCallback(async () => {
await conversation.endSession();
}, [conversation]);
// ...
}

Clienttool en dynamische variabelen

In de eerdere agentconfiguratie heb je de clienttool set_ui_state geregistreerd, zodat de agent tussen de verschillende UI-statussen kan navigeren. Om alles samen te brengen, moet je de implementatie van de clienttool doorgeven aan de opties van conversation.startSession.

Hier kun je ook de dynamische variabelen aan het gesprek doorgeven.

./page.tsx
const convId = await conversation.startSession({
signedUrl,
dynamicVariables: {
user_name: userName,
},
clientTools: {
set_ui_state: ({ step }: { step: string }): string => {
// Allow agent to navigate the UI.
setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
return `Navigated to ${step}`;
},
},
});

Documenten uploaden naar de kennisbank

In de stap Training vraagt de agent de gebruiker om documenten te uploaden of URL’s naar openbare websites in te dienen met informatie die beschikbaar moet zijn voor de agent. Hier kun je de nieuwe functie after van Next.js 15 gebruiken om documenten op de achtergrond te uploaden.

Maak een nieuwe server action upload om het maken van de kennisbank bij het verzenden van het formulier af te handelen. Sla de gespreks-ID en de kennisbank-ID’s in de Redis-database op zodra alle kennisbankdocumenten zijn gemaakt.

./app/actions/upload.ts
"use server";
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import { redirect } from "next/navigation";
import { after } from "next/server";
// Initialize Redis
const redis = Redis.fromEnv();
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
export async function uploadFormData(formData: FormData) {
const knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}> = [];
const files = formData.getAll("file-upload") as File[];
const email = formData.get("email-input");
const urls = formData.getAll("url-input");
const conversationId = formData.get("conversation-id");
after(async () => {
// Upload files as background job
// Create knowledge base entries
// Loop through files and create knowledge base entries
for (const file of files) {
if (file.size > 0) {
const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromFile({
file,
});
if (response.id) {
knowledgeBase.push({
id: response.id,
type: "file",
name: file.name,
});
}
}
}
// Append all urls
for (const url of urls) {
const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromUrl({
url: url as string,
});
if (response.id) {
knowledgeBase.push({
id: response.id,
type: "url",
name: `url for ${conversationId}`,
});
}
}
// Store knowledge base IDs and conversation ID in database.
const redisRes = await redis.set(
conversationId as string,
JSON.stringify({ email, knowledgeBase })
);
console.log({ redisRes });
});
redirect("/success");
}

De post-call webhook verwerken

De post-call webhook wordt geactiveerd wanneer een gesprek eindigt en de stappen voor analyse en gegevensextractie zijn voltooid.

Hier gebeuren enkele stappen, namelijk:

  1. Het webhooksecret verifiëren en de webhookpayload samenstellen.
  2. Een aangepast stemontwerp maken op basis van de voice_description.
  3. Een ElevenLabs-agent voor de gebruikers maken op basis van de agent_description die zij hebben opgegeven.
  4. De kennisbankdocumenten ophalen uit de gespreksstatus die in Redis is opgeslagen en de kennisbank aan de agent koppelen.
  5. De gebruiker een e-mail sturen om te melden dat de aangepaste ElevenLabs-agent klaar is om te chatten.
./app/api/convai-webhook/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import crypto from "crypto";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { Resend } from "resend";
import { EmailTemplate } from "@/components/email/post-call-webhook-email";
// Initialize Redis
const redis = Redis.fromEnv();
// Initialize Resend
const resend = new Resend(process.env.RESEND_API_KEY);
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
export async function GET() {
return NextResponse.json({ status: "webhook listening" }, { status: 200 });
}
export async function POST(req: NextRequest) {
const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET; // Add this to your env variables
const { event, error } = await constructWebhookEvent(req, secret);
if (error) {
return NextResponse.json({ error: error }, { status: 401 });
}
if (event.type === "post_call_transcription") {
const { conversation_id, analysis, agent_id } = event.data;
if (
agent_id === process.env.ELEVENLABS_AGENT_ID &&
analysis.evaluation_criteria_results.all_data_provided?.result === "success" &&
analysis.data_collection_results.voice_description?.value
) {
try {
// Design the voice
const voicePreview = await elevenlabs.textToVoice.createPreviews({
voiceDescription: analysis.data_collection_results.voice_description.value,
text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
});
const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
voiceName: `voice-${conversation_id}`,
voiceDescription: `Voice for ${conversation_id}`,
generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
});
// Get the knowledge base from redis
const redisRes = await getRedisDataWithRetry(conversation_id);
if (!redisRes) throw new Error("Conversation data not found!");
// Handle agent creation
const agent = await elevenlabs.conversationalAi.agents.create({
name: `Agent for ${conversation_id}`,
conversationConfig: {
tts: { voiceId: voice.voiceId },
agent: {
prompt: {
prompt:
analysis.data_collection_results.agent_description?.value ??
"You are a helpful assistant.",
knowledgeBase: redisRes.knowledgeBase,
},
firstMessage: "Hello, how can I help you today?",
},
},
});
console.log("Agent created", { agent: agent.agentId });
// Send email to user
console.log("Sending email to", redisRes.email);
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: redisRes.email,
subject: "Your ElevenLabs agent is ready to chat!",
react: EmailTemplate({ agentId: agent.agentId }),
});
} catch (error) {
console.error(error);
return NextResponse.json({ error }, { status: 500 });
}
}
}
return NextResponse.json({ received: true }, { status: 200 });
}
const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
const body = await req.text();
const signatureHeader = req.headers.get("ElevenLabs-Signature");
return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Laten we elke stap in detail doornemen.

Het webhooksecret verifiëren en de webhookpayload samenstellen

Wanneer het webhookrequest wordt ontvangen, verifiëren we eerst het webhooksecret en stellen we de webhookpayload samen.

./app/api/convai-webhook/route.ts
// ...
export async function POST(req: NextRequest) {
const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET;
const { event, error } = await constructWebhookEvent(req, secret);
// ...
}
// ...
const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
const body = await req.text();
const signatureHeader = req.headers.get("ElevenLabs-Signature");
return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Een aangepast stemontwerp maken op basis van de voice_description

Met de voice_description uit de webhookpayload maken we een aangepast stemontwerp.

./app/api/convai-webhook/route.ts
// ...
// Design the voice
const voicePreview = await elevenlabs.textToVoice.createPreviews({
voiceDescription: analysis.data_collection_results.voice_description.value,
text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
});
const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
voiceName: `voice-${conversation_id}`,
voiceDescription: `Voice for ${conversation_id}`,
generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
});
// ...

De kennisbankdocumenten ophalen uit de gespreksstatus die in Redis is opgeslagen

Het uploaden van de documenten kan langer duren dan de data-analyse van de webhook. Daarom moeten we de gespreksstatus in Redis blijven pollen totdat de documenten zijn geüpload.

./app/api/convai-webhook/route.ts
// ...
// Get the knowledge base from redis
const redisRes = await getRedisDataWithRetry(conversation_id);
if (!redisRes) throw new Error("Conversation data not found!");
// ...
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Een ElevenLabs-agent voor de gebruikers maken op basis van de agent_description die zij hebben opgegeven

Maak de ElevenLabs-agent voor de gebruiker op basis van de opgegeven agent_description en koppel het zojuist gemaakte stemontwerp en de kennisbank aan de agent.

./app/api/convai-webhook/route.ts
// ...
// Handle agent creation
const agent = await elevenlabs.conversationalAi.agents.create({
name: `Agent for ${conversationId}`,
conversationConfig: {
tts: { voiceId: voice.voiceId },
agent: {
prompt: {
prompt:
analysis.data_collection_results.agent_description?.value ??
"You are a helpful assistant.",
knowledgeBase: redisRes.knowledgeBase,
},
firstMessage: "Hello, how can I help you today?",
},
},
});
console.log("Agent created", { agent: agent.agentId });
// ...

De gebruiker een e-mail sturen om te melden dat de aangepaste ElevenLabs-agent klaar is om te chatten

Zodra de agent is gemaakt, kun je de gebruiker een e-mail sturen om te melden dat de aangepaste ElevenLabs-agent klaar is om te chatten.

./app/api/convai-webhook/route.ts
import { Resend } from "resend";
import { EmailTemplate } from "@/components/email/post-call-webhook-email";
// ...
// Send email to user
console.log("Sending email to", redisRes.email);
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: redisRes.email,
subject: "Your ElevenLabs agent is ready to chat!",
react: EmailTemplate({ agentId: agent.agentId }),
});
// ...

Je kunt new.email, een handige tool van het Resend-team, gebruiken om je e-mailsjablonen op gevoel te ontwerpen. Zodra je tevreden bent met de template, maak je een nieuw component en voeg je de agent-ID als prop toe.

./components/email/post-call-webhook-email.tsx
import {
Body,
Button,
Container,
Head,
Html,
Section,
Text,
Tailwind,
} from "@react-email/components";
import * as React from "react";
const EmailTemplate = (props: any) => {
const { agentId } = props;
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-[#151516] font-sans">
<Container className="mx-auto my-[40px] max-w-[600px] rounded-[8px] bg-[#0a1929] p-[20px]">
{/* Top Section */}
<Section className="mb-[32px] mt-[32px] text-center">
<Text className="m-0 text-[28px] font-bold text-[#9c27b0]">
Your ElevenLabs agent is ready to chat!
</Text>
</Section>
{/* Content Area with Icon */}
<Section className="mb-[32px] text-center">
{/* Circle Icon with Checkmark */}
<div className="mx-auto mb-[24px] flex h-[80px] w-[80px] items-center justify-center rounded-full bg-gradient-to-r from-[#9c27b0] to-[#3f51b5]">
<div className="text-[40px] text-white">✓</div>
</div>
{/* Descriptive Text */}
<Text className="mb-[24px] text-[18px] text-white">
Your ElevenLabs agent is ready to chat!
</Text>
</Section>
{/* Call to Action Button */}
<Section className="mb-[32px] text-center">
<Button
href={`https://elevenlabs.io/app/talk-to?agent_id=${agentId}`}
className="box-border rounded-[8px] bg-[#9c27b0] px-[40px] py-[20px] text-[24px] font-bold text-white no-underline"
>
Chat now!
</Button>
</Section>
{/* Footer */}
<Section className="mt-[40px] border-t border-[#2d3748] pt-[20px] text-center">
<Text className="m-0 text-[14px] text-white">
Powered by{" "}
<a
href="https://elevenlabs.io/conversational-ai"
target="_blank"
rel="noopener noreferrer"
className="underline transition-colors hover:text-gray-400"
>
ElevenLabs Agents
</a>
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export { EmailTemplate };

De app uitvoeren

Om de app lokaal van begin tot eind uit te voeren, moet je eerst de Next.js-ontwikkelserver starten en vervolgens in een aparte terminal de ngrok-tunnel uitvoeren om de webhookhandler toegankelijk te maken via internet.

  • Terminal 1:
    • Voer pnpm dev uit om de Next.js-ontwikkelserver te starten.
pnpm dev
  • Terminal 2:
    • Voer ngrok http 3000 uit om de webhookhandler toegankelijk te maken via internet.
ngrok http 3000

Open nu http://localhost:3000 en begin met het ontwerpen van je aangepaste ElevenLabs-agent, met je eigen stem!

Volgende stappen