Enviando áudio gerado pelo Twilio

Aprenda a integrar fala gerada a chamadas telefônicas com o Twilio.

Guia prático · Pressupõe que você tenha concluído o início rápido da ElevenAPI e tenha uma conta na Twilio.

Neste guia, você aprenderá a enviar uma mensagem gerada por IA em uma chamada telefônica usando a Twilio e a ElevenLabs. Esse processo permite enviar mensagens de voz de alta qualidade diretamente para quem liga para você.

Crie contas na Twilio e no ngrok

Usaremos a Twilio e o ngrok neste guia, então crie suas contas nessas plataformas.

Crie o servidor com Express

Inicialize seu projeto

Crie uma nova pasta para o projeto

mkdir elevenlabs-twilio
cd elevenlabs-twilio
npm init -y

Instale as dependências

npm install @elevenlabs/elevenlabs-js express express-ws twilio

Instale as dependências de desenvolvimento

npm i @types/node @types/express @types/express-ws @types/ws dotenv tsx typescript

Crie seus arquivos

// src/app.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";
import express, { Response } from "express";
import ExpressWs from "express-ws";
import { Readable } from "stream";
import VoiceResponse from "twilio/lib/twiml/VoiceResponse";
import { type WebSocket } from "ws";
const app = ExpressWs(express()).app;
const PORT: number = parseInt(process.env.PORT || "5000");
const elevenlabs = new ElevenLabsClient();
const voiceId = "aMSt68OGf4xUZAnLpTU8";
const outputFormat = "ulaw_8000";
const text = "This is a test. You can now hang up. Thank you.";
function startApp() {
app.post("/call/incoming", (_, res: Response) => {
const twiml = new VoiceResponse();
twiml.connect().stream({
url: `wss://${process.env.SERVER_DOMAIN}/call/connection`,
});
res.writeHead(200, { "Content-Type": "text/xml" });
res.end(twiml.toString());
});
app.ws("/call/connection", (ws: WebSocket) => {
ws.on("message", async (data: string) => {
const message: {
event: string;
start?: { streamSid: string; callSid: string };
} = JSON.parse(data);
if (message.event === "start" && message.start) {
const streamSid = message.start.streamSid;
const response = await elevenlabs.textToSpeech.convert(voiceId, {
modelId: "eleven_flash_v2_5",
outputFormat: outputFormat,
text,
});
const readableStream = Readable.from(response);
const audioArrayBuffer = await streamToArrayBuffer(readableStream);
ws.send(
JSON.stringify({
streamSid,
event: "media",
media: {
payload: Buffer.from(audioArrayBuffer as any).toString("base64"),
},
})
);
}
});
ws.on("error", console.error);
});
app.listen(PORT, () => {
console.log(`Local: http://localhost:${PORT}`);
console.log(`Remote: https://${process.env.SERVER_DOMAIN}`);
});
}
function streamToArrayBuffer(readableStream: Readable) {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
readableStream.on("data", (chunk) => {
chunks.push(chunk);
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks).buffer);
});
readableStream.on("error", reject);
});
}
startApp();
# .env
SERVER_DOMAIN=
ELEVENLABS_API_KEY=

Entenda o código

Lide com a chamada recebida

Quando você liga para seu número, a Twilio faz uma solicitação POST ao endpoint /call/incoming. Em seguida, usamos twiml.connect para informar à Twilio que queremos processar a chamada pelo nosso websocket, definindo a URL como nosso endpoint /call/connection.

function startApp() {
app.post('/call/incoming', (_, res: Response) => {
const twiml = new VoiceResponse();
twiml.connect().stream({
url: `wss://${process.env.SERVER_DOMAIN}/call/connection`,
});
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
});

Crie o texto em fala

Aqui, ouvimos as mensagens que a Twilio envia ao nosso endpoint websocket. Quando recebemos um evento de mensagem start, geramos áudio usando o SDK TypeScript da ElevenLabs.

app.ws('/call/connection', (ws: WebSocket) => {
ws.on('message', async (data: string) => {
const message: {
event: string;
start?: { streamSid: string; callSid: string };
} = JSON.parse(data);
if (message.event === 'start' && message.start) {
const streamSid = message.start.streamSid;
const response = await elevenlabs.textToSpeech.convert(voiceId, {
modelId: 'eleven_flash_v2_5',
outputFormat: outputFormat,
text,
});

Envie a mensagem

Ao receber o áudio da ElevenLabs, nós o convertemos em um array buffer e o enviamos à Twilio pelo websocket.

const readableStream = Readable.from(response);
const audioArrayBuffer = await streamToArrayBuffer(readableStream);
ws.send(
JSON.stringify({
streamSid,
event: "media",
media: {
payload: Buffer.from(audioArrayBuffer as any).toString("base64"),
},
})
);

Aponte o ngrok para sua aplicação

A Twilio exige uma URL acessível publicamente. Usaremos o ngrok para encaminhar a porta local da nossa aplicação e expô-la como uma URL pública.

Execute o seguinte comando no terminal:

ngrok http 5000

Copie o domínio do ngrok (sem https://) para usar nas variáveis de ambiente.

Atualize suas variáveis de ambiente

Atualize o arquivo .env com seu domínio do ngrok e a chave de API da ElevenLabs.

# .env
SERVER_DOMAIN=*******.ngrok.app
ELEVENLABS_API_KEY=*************************

Inicie a aplicação

Execute o seguinte comando para iniciar o app:

npm run dev

Configure a Twilio

Siga os guias da Twilio para criar um novo número. Depois de criá-lo, acesse a aba “Configure” em Phone Numbers -> Manage -> Active numbers

Na seção “A call comes in”, insira a URL completa da sua aplicação (não se esqueça de adicionar o caminho /call/incoming):

Ex.: https://***ngrok.app/call/incoming

Faça uma chamada telefônica

Ligue para seu número. Você deverá ouvir uma mensagem usando a voz da ElevenLabs.

Dicas para implantar em produção

Ao executar a aplicação em produção, defina a variável de ambiente SERVER_DOMAIN com o domínio do seu servidor. Não se esqueça também de atualizar a URL na Twilio para apontar para seu servidor de produção.

Conclusão

Agora você deve ter uma compreensão básica de como integrar a Twilio às vozes da ElevenLabs. Se tiver outras dúvidas ou sugestões para melhorar esta publicação, selecione o botão “Suggest edits” ou “Raise issue” abaixo.