Полное руководство по настройке ElevenLabs ConvAI виджета через Node.js прокси на VPS
Полное руководство по настройке
ElevenLabs ConvAI виджета
через Node.js прокси на VPS
Версия 1.0 | Октябрь 2025
Оглавление
- Часть 1: Введение и архитектура
- Часть 2: Подготовка сервера
- Часть 3: Настройка доменов и SSL
- Часть 4: Настройка Nginx
- Часть 5: Настройка Node.js прокси-сервера
- Часть 6: Настройка Flask приложения
- Часть 7: HTML код виджета
- Часть 8: Принцип работы перехвата
- Часть 9: Проверка и отладка
- Часть 10: Troubleshooting
- Часть 11: Безопасность
- Часть 12: Оптимизация
- Часть 13: Резюме и чек-лист
Часть 1: Введение и архитектура
1.1 Описание проблемы
ElevenLabs ConvAI виджет при прямом использовании сталкивается с двумя критическими проблемами:
- CORS (Cross-Origin Resource Sharing) ошибки – браузер блокирует запросы из-за отсутствия необходимых заголовков
Access-Control-Allow-Originот сервера ElevenLabs - Региональные блокировки – некоторые страны (включая Россию) не имеют прямого доступа к
api.elevenlabs.io
1.2 Решение
Создание промежуточного прокси-сервера на собственном VPS, который:
- Принимает все запросы от виджета (HTTP/HTTPS + WebSocket)
- Добавляет API ключ ElevenLabs на стороне сервера
- Проксирует запросы на официальный API ElevenLabs
- Добавляет необходимые CORS заголовки в ответы
- Отдаёт модифицированный виджет с подменёнными URL
1.3 Почему Node.js?
Node.js выбран по следующим причинам:
- Нативная поддержка WebSocket – критично для голосового общения
- Асинхронность – эффективная обработка множества одновременных соединений
- Богатая экосистема – готовые библиотеки для прокси (http-proxy, axios)
- Простота развертывания – минимальные зависимости
- Производительность – отлично справляется с задачами прокси
1.4 Финальная архитектура
┌─────────────────────────────────────────────────────────────┐
│ Браузер пользователя │
│ (Любая страна) │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS
↓
┌─────────────────────────────────────────────────────────────┐
│ agent.domain.ru (Nginx → Flask) │
│ - Основной сайт │
│ - Отдаёт HTML с виджетом │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS + WSS
↓
┌─────────────────────────────────────────────────────────────┐
│ eleven.domain.ru (Nginx → Node.js на порту 3000) │
│ - Прокси-сервер для ElevenLabs API │
│ - HTTP/HTTPS прокси для REST запросов │
│ - WebSocket прокси для аудио стриминга │
│ - Добавляет API ключ в заголовки │
│ - Добавляет CORS заголовки │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS + WSS + API Key
↓
┌─────────────────────────────────────────────────────────────┐
│ api.elevenlabs.io │
│ - Официальный API ElevenLabs │
└─────────────────────────────────────────────────────────────┘
1.5 Что происходит при вызове
1. Загрузка виджета:
- Браузер →
unpkg.com→ скачивается оригинальный JS виджета - JavaScript перехватчики подменяют URL API на
eleven.domain.ru
2. HTTP запросы (получение данных агента, голосов):
- Браузер →
eleven.domain.ru/v1/... - Node.js получает запрос → добавляет
xi-api-key→ отправляет наapi.elevenlabs.io/v1/... - Ответ от ElevenLabs → Node.js добавляет CORS → отправляет браузеру
3. WebSocket соединение (голосовое общение):
- Браузер →
wss://eleven.domain.ru/v1/convai/conversation - Nginx → WebSocket upgrade → Node.js
- Node.js → добавляет
xi-api-key→ устанавливает WebSocket кwss://api.elevenlabs.io - Двусторонний аудио-стриминг через прокси
Часть 2: Подготовка сервера
2.1 Требования
Минимальные системные требования:
- Ubuntu 20.04+ или Debian 11+
- 1 CPU core
- 1 GB RAM
- 10 GB disk
- Стабильное интернет-соединение
Необходимое ПО:
- Node.js 18+
- Nginx
- Certbot (для SSL)
- systemd (для автозапуска сервисов)
Доменные имена:
- Два поддомена, указывающих на IP вашего VPS
- Например:
agent.example.comиeleven.example.com
2.2 Подключение к серверу
ssh root@YOUR_SERVER_IP
# или
ssh username@YOUR_SERVER_IP
2.3 Обновление системы
sudo apt update
sudo apt upgrade -y
2.4 Установка Node.js
# Добавляем официальный репозиторий NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# Устанавливаем Node.js
sudo apt install -y nodejs
# Проверяем версии
node --version # Должно быть v20.x.x
npm --version # Должно быть v10.x.x
2.5 Установка Nginx
sudo apt install -y nginx
# Проверяем статус
sudo systemctl status nginx
# Включаем автозапуск
sudo systemctl enable nginx
2.6 Установка Certbot (для SSL)
sudo apt install -y certbot python3-certbot-nginx
2.7 Настройка firewall
# Устанавливаем ufw если нет
sudo apt install -y ufw
# Разрешаем SSH (ВАЖНО! Иначе потеряете доступ)
sudo ufw allow 22/tcp
# Разрешаем HTTP и HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Включаем firewall
sudo ufw enable
# Проверяем статус
sudo ufw status
Часть 3: Настройка доменов и SSL
3.1 Настройка DNS записей
В панели управления вашего доменного регистратора создайте A-записи:
| Тип | Имя | Значение | TTL |
|---|---|---|---|
| A | agent | YOUR_SERVER_IP | 3600 |
| A | eleven | YOUR_SERVER_IP | 3600 |
Проверка DNS:
# Дождитесь распространения DNS (до 24 часов, обычно 5-30 минут)
dig agent.domain.ru
dig eleven.domain.ru
Или
nslookup agent.domain.ru
nslookup eleven.domain.ru
3.2 Получение SSL сертификатов
Для первого домена (основной сайт):
sudo certbot certonly --standalone -d agent.domain.ru
Следуйте инструкциям:
- Введите email для уведомлений
- Согласитесь с условиями (Y)
- Откажитесь от рассылки (N)
Для второго домена (прокси):
sudo certbot certonly --standalone -d eleven.domain.ru
Сертификаты будут расположены в:
/etc/letsencrypt/live/agent.domain.ru/fullchain.pem/etc/letsencrypt/live/agent.domain.ru/privkey.pem/etc/letsencrypt/live/eleven.domain.ru/fullchain.pem/etc/letsencrypt/live/eleven.domain.ru/privkey.pem
Автоматическое обновление сертификатов:
# Certbot автоматически создаёт cron задачу
# Проверим что обновление работает:
sudo certbot renew --dry-run
Часть 4: Настройка Nginx
4.1 Конфигурация для основного сайта (agent.domain.ru)
sudo nano /etc/nginx/sites-available/agent.domain.ru
Содержимое файла:
# HTTP → HTTPS редирект
server {
listen 80;
listen [::]:80;
server_name agent.domain.ru;
# Редирект всех HTTP запросов на HTTPS
return 301 https://$host$request_uri;
}
HTTPS сервер
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name agent.domain.ru;
# SSL сертификаты
ssl_certificate /etc/letsencrypt/live/agent.domain.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/agent.domain.ru/privkey.pem;
# SSL параметры безопасности
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Проксирование на Flask приложение
location / {
proxy_pass http://127.0.0.1:8668;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket поддержка (если нужна)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
4.2 Конфигурация для прокси (eleven.domain.ru)
sudo nano /etc/nginx/sites-available/elevenlabs-proxy
Содержимое файла:
# HTTP → HTTPS редирект
server {
listen 80;
listen [::]:80;
server_name eleven.domain.ru;
return 301 https://$host$request_uri;
}
HTTPS сервер
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name eleven.domain.ru;
# SSL сертификаты
ssl_certificate /etc/letsencrypt/live/eleven.domain.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/eleven.domain.ru/privkey.pem;
# SSL параметры
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Проксирование на Node.js
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# КРИТИЧНО: WebSocket поддержка для голосового общения
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Увеличенные таймауты для длительных WebSocket соединений
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
# Буферизация
proxy_buffering off;
}
}
4.3 Активация конфигураций
# Создаём символические ссылки
sudo ln -s /etc/nginx/sites-available/agent.domain.ru /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/elevenlabs-proxy /etc/nginx/sites-enabled/
# Удаляем дефолтную конфигурацию (опционально)
sudo rm /etc/nginx/sites-enabled/default
# Проверяем конфигурацию на ошибки
sudo nginx -t
# Если всё OK, перезагружаем Nginx
sudo systemctl reload nginx
# Проверяем статус
sudo systemctl status nginx
Ожидаемый вывод nginx -t:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Часть 5: Настройка Node.js прокси-сервера
5.1 Создание структуры проекта
# Создаём директорию для проекта
sudo mkdir -p /var/www/eleven
cd /var/www/eleven
# Создаём директорию для статических файлов (если нужно)
sudo mkdir -p /var/www/eleven/convai-widget
# Устанавливаем права доступа
sudo chown -R $USER:$USER /var/www/eleven
5.2 Инициализация Node.js проекта
cd /var/www/eleven
# Создаём package.json
npm init -y
5.3 Установка необходимых пакетов
cd /var/www/eleven
# Устанавливаем зависимости
npm install express axios cors http-proxy
# Проверяем установку
ls node_modules/
Установленные пакеты и их назначение:
- express – веб-фреймворк для создания HTTP сервера
- axios – HTTP клиент для проксирования REST запросов
- cors – middleware для управления CORS заголовками
- http-proxy – библиотека для проксирования WebSocket соединений
5.4 Создание файла прокси-сервера
nano /var/www/eleven/proxy-server.js
Полный код proxy-server.js:
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const http = require('http');
const httpProxy = require('http-proxy');
// Конфигурация
const app = express();
const PORT = 3000;
const API_KEY = 'ВАШ_API_КЛЮЧ_ELEVENLABS'; // ЗАМЕНИТЕ НА СВОЙ!
// ============================================
// Middleware
// ============================================
// CORS для всех запросов
app.use(cors({
origin: '*',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
}));
// Парсинг JSON
app.use(express.json());
// ============================================
// Статические файлы (опционально)
// ============================================
// Если хотите отдавать модифицированный виджет
app.use('/convai-widget', express.static('/var/www/eleven/convai-widget'));
// ============================================
// Тестовый endpoint
// ============================================
app.get('/test', (req, res) => {
res.json({
status: 'ok',
message: 'ElevenLabs Proxy is running',
timestamp: new Date().toISOString()
});
});
// ============================================
// HTTP прокси для REST API запросов
// ============================================
app.use('/v1', async (req, res) => {
// Формируем полный URL
const url = `https://api.elevenlabs.io${req.originalUrl}`;
console.log(`→ HTTP ${req.method} ${url}`);
try {
// Конфигурация запроса
const config = {
method: req.method,
url: url,
headers: {
'xi-api-key': API_KEY,
'Content-Type': req.headers['content-type'] || 'application/json',
'User-Agent': 'ElevenLabs-Proxy/1.0'
},
responseType: 'stream',
validateStatus: () => true
};
// Добавляем тело запроса для POST/PUT
if (req.method !== 'GET' && req.method !== 'HEAD') {
config.data = req.body;
}
// Выполняем запрос к ElevenLabs API
const response = await axios(config);
console.log(`✓ Response: ${response.status}`);
// Копируем заголовки ответа
Object.keys(response.headers).forEach(key => {
res.set(key, response.headers[key]);
});
// Добавляем CORS заголовки
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Credentials', 'true');
// Устанавливаем статус код
res.status(response.status);
// Стримим данные обратно клиенту
response.data.pipe(res);
} catch (error) {
console.error(`✗ Error: ${error.message}`);
res.status(500).json({
error: 'Proxy error',
message: error.message
});
}
});
// ============================================
// HTTP сервер
// ============================================
const server = http.createServer(app);
// ============================================
// WebSocket прокси для голосового общения
// ============================================
const wsProxy = httpProxy.createProxyServer({
target: 'wss://api.elevenlabs.io',
ws: true,
changeOrigin: true,
secure: true
});
// Обработка ошибок WebSocket прокси
wsProxy.on('error', (err, req, socket) => {
console.error(`✗ WebSocket proxy error: ${err.message}`);
if (socket.writable) {
socket.end();
}
});
// Обработка WebSocket upgrade
server.on('upgrade', (req, socket, head) => {
console.log(`→ WebSocket upgrade: ${req.url}`);
// Добавляем API ключ в заголовки
req.headers['xi-api-key'] = API_KEY;
// Проксируем WebSocket соединение
wsProxy.ws(req, socket, head, {
target: 'wss://api.elevenlabs.io'
}, (err) => {
if (err) {
console.error(`✗ WebSocket upgrade error: ${err.message}`);
}
});
});
// ============================================
// Запуск сервера
// ============================================
server.listen(PORT, '127.0.0.1', () => {
console.log('='.repeat(50));
console.log(`ElevenLabs Proxy Server`);
console.log('='.repeat(50));
console.log(`Listening on: http://127.0.0.1:${PORT}`);
console.log(`HTTP/HTTPS: /v1/*`);
console.log(`WebSocket: wss://`);
console.log('='.repeat(50));
});
// Обработка ошибок сервера
server.on('error', (err) => {
console.error(`Server error: ${err.message}`);
process.exit(1);
});
// ============================================
// Graceful shutdown
// ============================================
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, closing server...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
⚠️ ВАЖНО: Замените API_KEY!
Найдите строку const API_KEY = 'ВАШ_API_КЛЮЧ_ELEVENLABS'; и замените на ваш реальный API ключ от ElevenLabs.
5.5 Тестирование прокси-сервера
Запуск вручную для проверки:
cd /var/www/eleven
node proxy-server.js
В другом терминале проверьте работу:
# Тест endpoint
curl http://127.0.0.1:3000/test
Тест API voices
curl http://127.0.0.1:3000/v1/voices
Если всё работает – нажмите Ctrl+C чтобы остановить сервер.
5.6 Создание systemd сервиса
sudo nano /etc/systemd/system/eleven-proxy.service
Содержимое файла:
[Unit]
Description=ElevenLabs Proxy Server
Documentation=https://docs.elevenlabs.io
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/eleven
ExecStart=/usr/bin/node /var/www/eleven/proxy-server.js
Автоматический перезапуск при падении
Restart=on-failure
RestartSec=5
Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=eleven-proxy
Переменные окружения
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Установка прав доступа:
# Устанавливаем владельца директории
sudo chown -R www-data:www-data /var/www/eleven
Устанавливаем права на файлы
sudo chmod 755 /var/www/eleven
sudo chmod 644 /var/www/eleven/proxy-server.js
Активация и запуск сервиса:
# Перезагружаем конфигурацию systemd
sudo systemctl daemon-reload
Включаем автозапуск при старте системы
sudo systemctl enable eleven-proxy
Запускаем сервис
sudo systemctl start eleven-proxy
Проверяем статус
sudo systemctl status eleven-proxy
Часть 6: Настройка Flask приложения
6.1 Структура Flask приложения
# Создаём директорию
sudo mkdir -p /home/$USER/agent
cd /home/$USER/agent
6.2 Простой Flask сервер
nano /home/$USER/agent/app.py
Минимальный app.py:
from flask import Flask, render_template_string
app = Flask(name)
HTML шаблон будет добавлен в следующей части
HTML_TEMPLATE = """
<!-- HTML код будет здесь -->
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
if name == 'main':
app.run(host='0.0.0.0', port=8668, debug=False)
6.3 Создание systemd сервиса для Flask
sudo nano /etc/systemd/system/agent-flask.service
Содержимое (замените $USER на ваше имя):
[Unit]
Description=Agent Flask Application
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=/home/$USER/agent
ExecStart=/usr/bin/python3 /home/$USER/agent/app.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Активация сервиса:
sudo systemctl daemon-reload
sudo systemctl enable agent-flask
sudo systemctl start agent-flask
sudo systemctl status agent-flask
Часть 7: HTML код виджета с перехватом запросов
7.1 Финальный рабочий HTML
Вставьте этот код в переменную HTML_TEMPLATE в app.py:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Мудрый советник</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
text-align: center;
padding: 50px 16px;
background: #ffffff;
margin: 0;
}
h1 {
font-size: 28px;
margin-bottom: 10px;
}
p {
font-size: 18px;
margin-bottom: 40px;
}
/* Обёртка виджета */
.widget-wrapper {
position: relative;
margin: 0 auto;
max-width: 360px;
width: 100%;
min-height: 320px;
}
/* Маска для скрытия "Powered by ElevenLabs" */
.powered-mask {
position: absolute;
bottom: 0;
right: 0;
left: 0;
width: 100%;
height: 50px;
background: #ffffff;
z-index: 999;
pointer-events: none;
}
/* Адаптивность */
@media (max-width: 400px) {
body {
padding: 40px 12px;
}
h1 {
font-size: 24px;
}
p {
font-size: 16px;
}
}
</style>
</head>
<body>
<h1>Мудрый советник</h1>
<p>Нажми кнопку, чтобы поговорить:</p>
<!-- КРИТИЧНО: Перехват запросов ПЕРЕД загрузкой виджета -->
<script>
(function() {
console.log('Setting up proxy intercepts...');
// Перехват fetch для HTTP запросов
const originalFetch = window.fetch;
window.fetch = function(url, options) {
if (typeof url === 'string') {
const originalUrl = url;
// Подменяем URL на наш прокси
url = url.replace('https://api.elevenlabs.io', 'https://eleven.t-ip.ru');
url = url.replace('https://api.us.elevenlabs.io', 'https://eleven.t-ip.ru');
if (originalUrl !== url) {
console.log('✓ Fetch redirected');
}
}
return originalFetch.call(this, url, options);
};
// Перехват WebSocket для голосового общения
const OriginalWebSocket = window.WebSocket;
window.WebSocket = function(url, protocols) {
if (typeof url === 'string') {
const originalUrl = url;
// Подменяем WebSocket URL на наш прокси
url = url.replace('wss://api.elevenlabs.io', 'wss://eleven.t-ip.ru');
url = url.replace('wss://api.us.elevenlabs.io', 'wss://eleven.t-ip.ru');
url = url.replace('ws://api.elevenlabs.io', 'ws://eleven.t-ip.ru');
url = url.replace('ws://api.us.elevenlabs.io', 'ws://eleven.t-ip.ru');
if (originalUrl !== url) {
console.log('✓ WebSocket redirected');
}
}
return new OriginalWebSocket(url, protocols);
};
// Копируем статические свойства WebSocket
Object.keys(OriginalWebSocket).forEach(key => {
window.WebSocket[key] = OriginalWebSocket[key];
});
console.log('✓ Proxy intercepts ready!');
})();
</script>
<!-- Виджет -->
<div class="widget-wrapper">
<elevenlabs-convai agent-id="6801k7pb4wcfe1q941kprbdpwsqr"></elevenlabs-convai>
<div class="powered-mask"></div>
</div>
<!-- Загрузка виджета с unpkg.com -->
<script src="https://unpkg.com/@elevenlabs/convai-widget-embed" async type="text/javascript"></script>
</body>
</html> ⚠️ Важно: Замените 6801k7pb4wcfe1q941kprbdpwsqr на ваш реальный Agent ID из панели ElevenLabs.
Часть 8: Принцип работы перехвата запросов
8.1 Как работает перехват fetch()
Теория: window.fetch() – это встроенная браузерная функция для HTTP запросов. Мы переопределяем её, сохраняя оригинал:
// 1. Сохраняем оригинальную функцию
const originalFetch = window.fetch;
// 2. Создаём свою обёртку
window.fetch = function(url, options) {
// 3. Изменяем URL если нужно
if (typeof url === 'string') {
url = url.replace('https://api.elevenlabs.io', 'https://eleven.domain.ru');
}
// 4. Вызываем оригинальную функцию с изменённым URL
return originalFetch.call(this, url, options);
};
Что происходит:
- Виджет вызывает
fetch('https://api.elevenlabs.io/v1/voices') - Наша обёртка перехватывает вызов
- URL заменяется на
https://eleven.domain.ru/v1/voices - Запрос идёт на наш прокси вместо ElevenLabs
- Node.js получает запрос, добавляет API ключ
- Node.js отправляет на реальный ElevenLabs API
- Ответ возвращается обратно через всю цепочку
8.2 Как работает перехват WebSocket
Теория: WebSocket – это протокол для двусторонней связи в реальном времени. Принцип перехвата аналогичен fetch:
// 1. Сохраняем оригинальный конструктор
const OriginalWebSocket = window.WebSocket;
// 2. Создаём свой конструктор
window.WebSocket = function(url, protocols) {
// 3. Изменяем URL
if (typeof url === 'string') {
url = url.replace('wss://api.elevenlabs.io', 'wss://eleven.domain.ru');
}
// 4. Создаём WebSocket с новым URL
return new OriginalWebSocket(url, protocols);
};
// 5. ВАЖНО: Копируем статические свойства
Object.keys(OriginalWebSocket).forEach(key => {
window.WebSocket[key] = OriginalWebSocket[key];
});
Поток данных при голосовом вызове:
- Пользователь нажимает “Start a call”
- Виджет пытается открыть WebSocket:
wss://api.elevenlabs.io/v1/convai/conversation - Наш перехватчик меняет URL на:
wss://eleven.domain.ru/v1/convai/conversation - Браузер → Nginx (eleven.domain.ru)
- Nginx видит WebSocket Upgrade → проксирует на Node.js (порт 3000)
- Node.js получает Upgrade запрос
- Node.js добавляет заголовок ‘xi-api-key’
- Node.js устанавливает WebSocket соединение с реальным ElevenLabs API
- Двусторонняя передача аудио-данных через прокси
8.3 Почему это работает
Ключевые моменты:
- JavaScript выполняется ПЕРЕД загрузкой виджета – перехватчики устанавливаются до того как виджет попытается сделать запросы
- Виджет не знает о подмене – для него это выглядит как обычные вызовы fetch() и WebSocket()
- Прокси прозрачный – добавляет только API ключ и CORS заголовки, не изменяя данные
- WebSocket проходит через всю цепочку – Nginx правильно обрабатывает Upgrade, Node.js корректно проксирует бинарные данные
Часть 9: Проверка и отладка
9.1 Проверка инфраструктуры
Шаг 1: Проверка DNS
dig agent.domain.ru +short
dig eleven.domain.ru +short
# Оба должны вернуть IP вашего сервера
Шаг 2: Проверка SSL сертификатов
curl -I https://agent.domain.ru
curl -I https://eleven.domain.ru/test
# Не должно быть ошибок SSL
Шаг 3: Проверка сервисов
sudo systemctl status nginx
sudo systemctl status eleven-proxy
sudo systemctl status agent-flask
# Все должны быть active (running)
Шаг 4: Проверка портов
sudo ss -tlnp | grep -E ':(80|443|3000|8668)'
# :80 - nginx, :443 - nginx, :3000 - node, :8668 - python3
9.2 Проверка Node.js прокси
# Health check
curl http://127.0.0.1:3000/test
# API voices
curl http://127.0.0.1:3000/v1/voices | jq '.'
# Через домен
curl https://eleven.domain.ru/test
curl https://eleven.domain.ru/v1/voices
9.3 Проверка в браузере
Откройте https://agent.domain.ru и откройте DevTools (F12):
1. Вкладка Console – должны появиться:
Setting up proxy intercepts...
✓ Proxy intercepts ready!
✓ Fetch redirected
✓ WebSocket redirected
2. Вкладка Network – все запросы на eleven.domain.ru:
- Fetch/XHR: запросы к
eleven.domain.ru/v1/... - WS: WebSocket к
eleven.domain.ru/v1/convai/conversation
9.4 Просмотр логов
# Логи Node.js прокси
sudo journalctl -u eleven-proxy -f
# Логи Flask
sudo journalctl -u agent-flask -f
# Логи Nginx
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
Часть 10: Troubleshooting (Решение проблем)
10.1 Проблема: 502 Bad Gateway
Причина: Node.js прокси не запущен или недоступен.
Решение:
sudo systemctl status eleven-proxy
sudo systemctl start eleven-proxy
sudo journalctl -u eleven-proxy -n 50 --no-pager
sudo ss -tlnp | grep 3000
10.2 Проблема: CORS ошибки
Причина: Node.js не добавляет CORS заголовки.
Решение: Проверьте что в proxy-server.js есть:
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Credentials', 'true');
app.use(cors({ origin: '*', credentials: true }));
10.3 Проблема: WebSocket не подключается
Причина: Node.js не обрабатывает WebSocket Upgrade правильно.
Решение:
- Проверьте Nginx конфиг – должны быть строки для WebSocket
- Проверьте Node.js – должен быть обработчик
server.on('upgrade') - Перезапустите сервисы
10.4 Проблема: 404 Not Found
Причина: Неправильная конструкция URL в Node.js.
Решение: Убедитесь что используется req.originalUrl:
const url = `https://api.elevenlabs.io${req.originalUrl}`;
10.5 Проблема: Высокая задержка
Причина: Прокси добавляет hop, медленная сеть.
Решение:
- Используйте сервер близко к пользователям
- Проверьте
ping api.elevenlabs.io - Оптимизируйте Node.js конфиг
Часть 11: Безопасность
11.1 Защита API ключа
КРИТИЧНО: API ключ ElevenLabs должен быть на сервере, НИКОГДА в браузере!
Дополнительная защита – переменные окружения:
# Создаём .env файл
sudo nano /var/www/eleven/.env
Содержимое:
ELEVENLABS_API_KEY=sk_ваш_ключ
NODE_ENV=production
PORT=3000
Защищаем файл
sudo chmod 600 /var/www/eleven/.env
sudo chown www-data:www-data /var/www/eleven/.env
В proxy-server.js:
require('dotenv').config();
const API_KEY = process.env.ELEVENLABS_API_KEY;
11.2 Rate limiting
npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests'
});
app.use('/v1', limiter);
11.3 Fail2Ban
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Часть 12: Оптимизация и масштабирование
12.1 Кэширование в Nginx
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
12.2 Сжатие Gzip
# В /etc/nginx/nginx.conf
http {
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript;
}
12.3 PM2 для Node.js
sudo npm install -g pm2
pm2 start proxy-server.js --name eleven-proxy
pm2 startup
pm2 save
pm2 monit
Часть 13: Резюме и чек-лист
13.1 Что мы настроили
- VPS сервер с Ubuntu
- Два домена с SSL сертификатами
- Nginx как reverse proxy
- Node.js прокси-сервер для ElevenLabs API
- Flask приложение для основного сайта
- HTML виджет с перехватом запросов
- Systemd сервисы для автозапуска
- Логирование и мониторинг
13.2 Чек-лист для нового сервера
Подготовка:
- Получить VPS с Ubuntu 20.04+
- Настроить DNS A-записи для двух доменов
- Подключиться по SSH
Установка:
- Обновить систему:
apt update && apt upgrade - Установить Node.js 20+
- Установить Nginx
- Установить Certbot
- Настроить firewall (ufw)
SSL сертификаты:
- Получить SSL для agent.domain.com
- Получить SSL для eleven.domain.com
Nginx:
- Создать конфиг для agent.domain.com
- Создать конфиг для eleven.domain.com
- Активировать конфиги
- Проверить:
nginx -t - Перезагрузить:
systemctl reload nginx
Node.js прокси:
- Создать директорию
/var/www/eleven - Создать
package.json - Установить пакеты
- Создать
proxy-server.js - Заменить API_KEY на свой!
- Протестировать вручную
- Создать systemd сервис
- Запустить и включить автозапуск
Flask приложение:
- Создать
app.py - Вставить HTML код с виджетом
- Заменить agent-id на свой!
- Создать systemd сервис
- Запустить и включить автозапуск
Проверка:
- Открыть https://agent.domain.com
- Проверить Console на перехват
- Проверить Network на запросы
- Нажать “Start a call”
- Проверить голосовое общение
Заключение
Теперь у вас есть полнофункциональная инфраструктура для работы с ElevenLabs ConvAI виджетом:
- ✅ Работает из любой страны без VPN
- ✅ API ключ защищён на сервере
- ✅ Нет CORS ошибок
- ✅ Полная поддержка WebSocket
- ✅ Автоматический запуск сервисов
- ✅ SSL сертификаты
- ✅ Логирование
Этот мануал можно использовать для развёртывания на любом другом VPS сервере. Просто следуйте инструкциям шаг за шагом и заменяйте домены, API ключи и agent-id на свои.
Удачи! 🚀
© 2025 | Версия 1.0
