> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# 通过 Twilio 发送生成的音频

> **Note**
>
> **操作指南** · 假设你已完成 [ElevenAPI 快速入门](/docs/zh/eleven-api/quickstart)，并拥有 Twilio 账户。

本指南将介绍如何使用 Twilio 和 ElevenLabs 通过电话发送 AI 生成的消息。此流程可将高质量语音消息直接发送给来电者。

## 创建 Twilio 和 ngrok 账户

本指南将使用 Twilio 和 ngrok，请先创建相应账户。

* [twilio.com](https://www.twilio.com)
* [ngrok.com](https://ngrok.com)

## 使用 Express 创建服务器

### 初始化项目

为项目创建新文件夹。

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

### 安装依赖项

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

### 安装开发依赖项

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

### 创建文件

```ts
// 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
# .env
SERVER_DOMAIN=
ELEVENLABS_API_KEY=
```

## 了解代码

### 处理来电

当有人拨打号码时，Twilio 会向 `/call/incoming` 端点发送 POST 请求。
随后，我们使用 twiml.connect，通过将 url 设置为 `/call/connection` 端点，告知 Twilio 要通过 WebSocket 处理通话。

```ts
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());
  });
```

### 创建文本转语音

此处会监听 Twilio 发送到 WebSocket 端点的消息。收到 `start` 消息事件时，会使用 ElevenLabs [TypeScript SDK](https://github.com/elevenlabs/elevenlabs-js) 生成音频。

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

### 发送消息

从 ElevenLabs 收到音频后，将其转换为数组缓冲区，并通过 WebSocket 将音频发送给 Twilio。

```ts
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"),
    },
  })
);
```

## 将 ngrok 指向应用

Twilio 需要可公开访问的 URL。我们将使用 ngrok 转发应用的本地端口，并将其公开为公共 URL。

在终端中运行以下命令：

```
ngrok http 5000
```

复制 ngrok 域名（不含 https\://），用于环境变量。

![](/docs/_fern-img/830a701ecee4b2bd32f44a891806a91e00f27dcf263c5917fa20a91a3353d6a1.webp)

## 更新环境变量

使用 ngrok 域名和 ElevenLabs API 密钥更新 `.env` 文件。

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

## 启动应用

运行以下命令启动应用：

```
npm run dev
```

## 设置 Twilio

按照 Twilio 指南创建新号码。创建号码后，前往 Phone Numbers -> Manage -> Active numbers 中的“Configure”标签页。

在“A call comes in”部分，输入应用的完整 URL（务必添加 `/call/incoming` 路径）：

例如：https\://**\*\*\***&#x6E;grok.app/call/incoming

![](/docs/_fern-img/8f57f550bca8ac451980d98482ff5877c4f8fac30035e712cd0375a6527686ff.webp)

## 拨打电话

拨打该号码。你应该会听到使用 ElevenLabs 音色的消息。

## 部署到生产环境的提示

在生产环境中运行应用时，请确保将 `SERVER_DOMAIN` 环境变量设置为服务器域名。同时务必更新 Twilio 中的 URL，使其指向生产服务器。

## 总结

现在你应该已基本了解如何将 Twilio 与 ElevenLabs 音色集成。如有其他问题，或对如何改进这篇博客文章有建议，欢迎点击下方“Suggest edits”或“Raise issue”按钮。