Coleta e análise de dados com a Plataforma de agentes no Next.js

Colete e analise dados em webhooks pós-chamada usando a Plataforma de agentes e o Next.js.

Tutorial · Pressupõe que você concluiu o início rápido do ElevenAgents e configurou um projeto Next.js.

Introdução

Neste tutorial, você aprenderá a criar um agente de voz que coleta informações do usuário por meio de uma conversa, depois analisa e extrai os dados de forma estruturada e os envia ao seu aplicativo pelo webhook pós-chamada.

Requisitos

  • Uma conta da ElevenLabs com uma chave de API.
  • Node.js v18 ou superior instalado na sua máquina.

Configuração

Crie um novo projeto Next.js

Recomendamos usar nosso modelo da Plataforma de agentes no v0.dev como ponto de partida para seu aplicativo. Este modelo é um aplicativo Next.js pronto para produção com o agente da ElevenLabs já integrado.

Configure a Plataforma de agentes

Siga nosso guia do Next.js para ver as etapas de instalação e configuração. Depois, volte aqui para criar os recursos avançados.

Configuração do agente

1

Faça login na ElevenLabs

Acesse elevenlabs.io e entre na sua conta.

2

Crie um novo agente

Acesse Agents Platform > Agents e crie um novo agente usando o modelo em branco.

3

Defina a primeira mensagem

Defina a primeira mensagem e especifique a variável dinâmica da plataforma.

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

Defina o prompt do sistema

Defina o prompt do sistema. Você também pode incluir variáveis dinâmicas aqui.

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

Configure as ferramentas do cliente

Configure a ferramenta de cliente a seguir para navegar entre as etapas:

  • Nome: set_ui_state
    • Descrição: Use esta ferramenta do lado do cliente para navegar entre os diferentes estados da interface.
    • Aguardar resposta: true
    • Tempo limite de resposta (segundos): 1
    • Parâmetros:
      • Tipo de dados: string
      • Identificador: step
      • Obrigatório: true
      • Tipo de valor: LLM Prompt
      • Descrição: A etapa para a qual navegar na interface. Use apenas as etapas definidas no prompt do sistema!
6

Defina a voz do seu agente

Acesse a aba Voice e defina a voz do seu agente. Você pode encontrar uma lista de vozes recomendadas para a Agents Platform na documentação sobre Design de Voz Conversacional.

7

Defina os critérios de avaliação

Acesse a aba Analysis e adicione um novo critério de avaliação.

  • Nome: all_data_provided
    • Prompt: Avalie se o usuário forneceu uma descrição do agente que deseja gerar e uma descrição da voz que o agente deve ter.
8

Configure a coleta de dados

Você pode usar a análise pós-chamada para extrair dados da conversa. Na aba Analysis, em Data Collection, adicione os itens a seguir:

  • Identificador: voice_description
    • data-type: String
    • Descrição: Com base na descrição da voz que o usuário quer que o agente tenha, gere uma descrição concisa da voz, incluindo idade, sotaque, tom e personalidade, se disponível.
  • Identificador: agent_description
    • data-type: String
    • Descrição: Com base na descrição do agente que o usuário deseja criar, gere um prompt que possa ser usado para treinar um modelo para atuar como o agente.
9

Configure o webhook pós-chamada

Os webhooks pós-chamada são usados para avisar você quando uma chamada termina e as etapas de análise e extração de dados foram concluídas.

Neste exemplo, o webhook pós-chamada realiza algumas etapas:

  1. Cria um design de voz personalizado com base em voice_description.
  2. Cria um agente da ElevenLabs para os usuários com base no agent_description fornecido.
  3. Recupera os documentos da base de conhecimento do estado da conversa armazenado no Redis e anexa a base de conhecimento ao agente.
  4. Envia um e-mail ao usuário avisando que seu agente personalizado da ElevenLabs está pronto para conversar.

Ao executar localmente, você precisará de uma ferramenta como o ngrok para expor seu servidor local à internet.

ngrok http 3000

Acesse as configurações da Agents Platform e, em Post-Call Webhook, crie um novo webhook e cole sua URL do ngrok: https://<your-url>.ngrok-free.app/api/convai-webhook.

Após salvar o webhook, você receberá um segredo de webhooks. Armazene esse segredo com segurança, pois será necessário defini-lo posteriormente no arquivo .env.

Integre os recursos avançados

Configure um banco de dados Redis para armazenar o estado da conversa

Neste exemplo, usamos o Redis para armazenar o estado da conversa. Isso permite recuperar os documentos da base de conhecimento do estado da conversa após o término da chamada.

Se você estiver fazendo deploy na Vercel, poderá configurar a integração Upstash for Redis ou, como alternativa, criar uma conta gratuita na Upstash e criar um novo banco de dados.

Configure o Resend para enviar e-mails pós-chamada

Neste exemplo, usamos o Resend para enviar o e-mail pós-chamada ao usuário. Para isso, você precisará criar uma conta gratuita no Resend e configurar uma nova chave de API.

Defina as variáveis de ambiente

Na raiz do seu projeto, crie um arquivo .env e adicione as seguintes variáveis:

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=

Configure a segurança e a autenticação

Para proteger seu agente da ElevenLabs, você precisa habilitar a autenticação na aba Security da configuração do agente.

Quando a autenticação estiver habilitada, você precisará criar uma URL assinada em um ambiente seguro do lado do servidor para iniciar uma conversa com o agente. No Next.js, você pode fazer isso configurando uma nova rota de API.

./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 });
}
}

Inicie a sessão de conversa

Para iniciar a conversa, primeiro chame sua rota de API para obter a URL assinada e depois use o hook useConversation para configurar a sessão de conversa.

./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]);
// ...
}

Ferramenta de cliente e variáveis dinâmicas

Na configuração do agente anterior, você registrou a ferramenta de cliente set_ui_state para permitir que o agente navegue entre os diferentes estados da interface. Para reunir tudo, você precisa passar a implementação da ferramenta de cliente para as opções de conversation.startSession.

É também aqui que você pode passar as variáveis dinâmicas para a conversa.

./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}`;
},
},
});

Envio de documentos para a base de conhecimento

Na etapa Training, o agente pedirá ao usuário que envie documentos ou URLs de sites públicos com informações que devem estar disponíveis para seu agente. Aqui, você pode usar a nova função after do Next.js 15 para permitir o envio de documentos em segundo plano.

Crie uma nova ação de servidor upload para processar a criação da base de conhecimento quando o formulário for enviado. Depois que todos os documentos da base de conhecimento forem criados, armazene o ID da conversa e os IDs da base de conhecimento no banco de dados Redis.

./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");
}

Processar o webhook pós-chamada

O webhook pós-chamada é acionado quando uma chamada termina e as etapas de análise e extração de dados são concluídas.

Aqui acontecem algumas etapas:

  1. Verificar o segredo do webhook e criar o payload do webhook.
  2. Criar um design de voz personalizado com base em voice_description.
  3. Criar um agente da ElevenLabs para os usuários com base na agent_description fornecida por eles.
  4. Recuperar os documentos da base de conhecimento do estado da conversa armazenado no Redis e anexar a base de conhecimento ao agente.
  5. Enviar um e-mail ao usuário para avisar que seu agente personalizado da ElevenLabs está pronto para conversar.
./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;
}

Vamos analisar cada etapa em detalhes.

Verificar o segredo do webhook e criar o payload do webhook

Quando a solicitação do webhook é recebida, primeiro verificamos o segredo do webhook e criamos o payload do webhook.

./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;
}

Criar um design de voz personalizado com base em voice_description

Usando voice_description do payload do webhook, criamos um design de voz personalizado.

./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,
});
// ...

Recuperar os documentos da base de conhecimento do estado da conversa armazenado no Redis

O upload dos documentos pode levar mais tempo do que a análise dos dados do webhook. Por isso, precisamos consultar o estado da conversa no Redis até que os documentos sejam enviados.

./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;
}

Criar um agente da ElevenLabs para os usuários com base na agent_description fornecida

Crie o agente da ElevenLabs para o usuário com base na agent_description fornecida e anexe o design de voz e a base de conhecimento recém-criados ao agente.

./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 });
// ...

Enviar um e-mail ao usuário para avisar que seu agente personalizado da ElevenLabs está pronto para conversar

Depois que o agente é criado, você pode enviar um e-mail ao usuário para avisar que seu agente personalizado da ElevenLabs está pronto para conversar.

./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 }),
});
// ...

Você pode usar o new.email, uma ferramenta prática da equipe da Resend, para criar visualmente seus templates de e-mail. Quando estiver satisfeito com o template, crie um novo componente e adicione o ID do agente como uma prop.

./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 };

Executar o app

Para executar o app localmente de ponta a ponta, primeiro você precisará iniciar o servidor de desenvolvimento do Next.js e, em seguida, em outro terminal, iniciar o túnel do ngrok para expor o manipulador de webhooks à internet.

  • Terminal 1:
    • Execute pnpm dev para iniciar o servidor de desenvolvimento do Next.js.
pnpm dev
  • Terminal 2:
    • Execute ngrok http 3000 para expor o manipulador de webhooks à internet.
ngrok http 3000

Agora, abra http://localhost:3000 e comece a criar seu agente personalizado da ElevenLabs, com a sua voz!

Próximas etapas