Настройка WhatsApp

К WhatsApp Hermes подключается через встроенный мост на базе Baileys. Он эмулирует сессию WhatsApp Web и работает в обход официального WhatsApp Business API. Аккаунт разработчика Meta или верификация Business не нужны.

Запустите hermes gateway setup и выберите WhatsApp — мастер проведёт вас по шагам.

совет
Два способа интеграции с WhatsApp

Эта страница посвящена мосту Baileys — быстрая настройка, личные аккаунты, публичный URL не нужен, но есть риск бана.

Если вы строите настоящего бизнес-бота и вам важна стабильность, смотрите руководство по WhatsApp Business Cloud API. Это официальный путь, поддерживаемый Meta: никакого риска блокировки аккаунта, но потребуется бизнес-аккаунт Meta и публичный URL для вебхука.

При желании оба адаптера можно запускать параллельно на разных номерах телефонов.

внимание
Неофициальный API — риск бана

За пределами Business API сторонние боты официально не поддерживаются. Любой сторонний мост несёт небольшой риск ограничений на аккаунт. Чтобы его снизить:

  • Заведите отдельный номер телефона под бота (не свой личный)
  • Не рассылайте массовые сообщения и спам — держите общение в живом, разговорном формате
  • Не автоматизируйте исходящие тем, кто вам ещё не писал
внимание
Обновления протокола WhatsApp Web

Протокол WhatsApp Web время от времени меняется, и это может временно ломать совместимость со сторонними мостами. В таких случаях Hermes обновляет зависимость моста. Если после очередного обновления WhatsApp бот перестал отвечать — подтяните свежую версию Hermes и выполните повторную привязку.

Несколько профилей

Мультиплексор хоста может обслуживать отдельную привязанную сессию WhatsApp для каждого профиля. Чтобы привязать вторичный профиль, выполните hermes -p work whatsapp, затем включите WhatsApp для этого профиля. Включённый профиль без creds.json пропускается со статусом whatsapp_unpaired и командой привязки.

Явный platforms.whatsapp.extra.bridge_port имеет приоритет. Иначе вторичный профиль выбирает первый свободный порт в диапазоне 3001–3999, который не занят записью другого профиля, и сохраняет его в собственном файле platforms/whatsapp/bridge_port для следующих запусков. Операторы могут заранее создать этот файл с номером порта; чтобы выделить новый порт, удалите файл. Профиль запуска использует порт 3000, если у него ещё нет такого файла (он появляется, когда профиль побывал вторичным): тогда его шлюз и hermes send --to whatsapp:<chat_id> продолжают использовать записанный порт.

Вторичный профиль подхватывает уже работающий на его порту мост, только если собственный pid-файл его сессии указывает на этот процесс (pid, время старта в ядре и порт, на котором он запущен): именно это остаётся после аварии шлюза. Нездоровый мост по той же идентичности завершается и запускается заново. Любой другой процесс, занявший порт, — фатальная ошибка только для этого профиля. Задайте в platforms.whatsapp.extra.bridge_port другой свободный порт или остановите процесс, который его держит. Остальные профили продолжают работать. hermes gateway status --profile work показывает собственный адаптер WhatsApp этого профиля, а не общий вход.

Профили, у каждого из которых свой шлюз (а не один мультиплексированный), по умолчанию все используют порт 3000. Назначьте каждому свой порт в config.yaml этого профиля:

platforms:
  whatsapp:
    extra:
      bridge_port: 3001        # one distinct port per profile

Шлюз определяет работающий мост по каталогу сессии, который мост сообщает в /health. Мост, обслуживающий сессию другого профиля, не подхватывается и не останавливается никогда: WhatsApp второго профиля не запускается с ошибкой whatsapp_bridge_foreign_session, где указаны порт и чужая сессия. Процесс, который держит порт, но не отвечает на /health вовремя, тоже остаётся работать, а WhatsApp завершается с повторяемой ошибкой whatsapp_bridge_unresponsive. hermes send --to whatsapp:<chat_id> и доставка из cron проверяют то же поле и ничего не отправляют через мост другого профиля или через мост, у которого /health не отвечает. Если вы переопределяете session_path, делайте его разным для каждого профиля, иначе профили будут делить один вход в WhatsApp. Мосты, запущенные более старым Hermes, не сообщают каталог сессии и один раз перезапускаются, как после обновления моста.

Два режима

РежимКак работаетКому подойдёт
Отдельный номер для бота (рекомендуется)Под бота выделяется отдельный телефонный номер. Люди пишут прямо на него.Чистый UX, несколько пользователей, меньший риск бана
Самочат на личном номереИспользуется ваш собственный WhatsApp. Чтобы пообщаться с агентом, вы пишете самому себе.Быстрый старт, один пользователь, тесты

Что понадобится

  • Node.js v18+ и npm — мост WhatsApp запускается как процесс Node.js
  • Телефон с установленным WhatsApp (для сканирования QR-кода)

В отличие от старых мостов на основе браузера, нынешний вариант на Baileys обходится без локального Chromium и стека Puppeteer.


Шаг 1. Запустите мастер настройки

hermes whatsapp

Мастер сделает следующее:

  1. Спросит, какой режим вам нужен (bot или self-chat)
  2. При необходимости установит зависимости моста
  3. Выведет QR-код прямо в терминал
  4. Подождёт, пока вы его отсканируете

Как отсканировать QR-код:

  1. Откройте WhatsApp на телефоне
  2. Зайдите в Настройки → Связанные устройства
  3. Нажмите Привязка устройства
  4. Наведите камеру на QR-код в терминале

После привязки мастер подтвердит соединение и завершится. Сессия сохраняется автоматически.

совет

Если QR-код выглядит искажённым, проверьте, что ширина терминала не меньше 60 столбцов и он поддерживает Unicode. Иногда помогает другой эмулятор терминала.


Шаг 2. Где взять второй номер (режим бота)

Для режима бота нужен номер, ещё не зарегистрированный в WhatsApp. Есть три варианта:

ВариантСтоимостьПримечания
Google VoiceБесплатноТолько для США. Получите номер на voice.google.com. Подтвердите WhatsApp по SMS через приложение Google Voice.
Предоплаченная SIM$5–15 разовоЛюбой оператор. Активируйте, подтвердите WhatsApp — дальше SIM может лежать в ящике. Номер нужно держать активным (звонок раз в 90 дней).
VoIP-сервисыот бесплатного до $5/месTextNow, TextFree и аналоги. Часть VoIP-номеров WhatsApp блокирует — если первый не сработал, попробуйте пару других.

Когда номер на руках:

  1. Установите WhatsApp на телефон (либо используйте приложение WhatsApp Business с двумя SIM)
  2. Зарегистрируйте новый номер в WhatsApp
  3. Запустите hermes whatsapp и отсканируйте QR-код из этого аккаунта WhatsApp

Шаг 3. Настройте Hermes

Добавьте в файл ~/.hermes/.env следующее:

# Обязательно
WHATSAPP_ENABLED=true
WHATSAPP_MODE=bot                          # "bot" или "self-chat"

# Контроль доступа — выберите ОДИН из вариантов:
WHATSAPP_ALLOWED_USERS=15551234567         # Номера телефонов через запятую (с кодом страны, без +)
# WHATSAPP_ALLOWED_USERS=*                 # ИЛИ * — разрешить всех
# WHATSAPP_ALLOW_ALL_USERS=true            # ИЛИ этот флаг (то же, что *)
совет
Сокращение «разрешить всех»

Значение WHATSAPP_ALLOWED_USERS=* пропускает любых отправителей (то же, что WHATSAPP_ALLOW_ALL_USERS=true). Поведение согласовано со списками разрешений для групп Signal. Хотите вместо этого пользоваться привязкой по паролю — уберите обе переменные и положитесь на систему DM-привязки.

Дополнительные настройки поведения в ~/.hermes/config.yaml:

unauthorized_dm_behavior: pair

whatsapp:
  unauthorized_dm_behavior: ignore
  • unauthorized_dm_behavior: pair — глобальное значение по умолчанию. Неизвестным отправителям личных сообщений выдаётся код привязки.
  • whatsapp.unauthorized_dm_behavior: ignore заставляет WhatsApp молчать в ответ на неавторизованные личные сообщения — для приватного номера обычно вариант получше.

Групповые чаты (режим бота)

Группы фильтруются групповой политикой, а не списком разрешённых для личных сообщений. WHATSAPP_GROUP_POLICY / whatsapp.group_policy по умолчанию равно pairing — из групп тогда ничего не пересылается. allowlist вместе с WHATSAPP_GROUP_ALLOWED_USERS / whatsapp.group_allow_from (JID групп через запятую, например 120363001234567890@g.us) пропускает перечисленные группы; open пропускает все группы, где состоит бот. Затем отправитель проверяется, как любой другой участник шлюза: если задан WHATSAPP_ALLOWED_USERS, участник должен быть в нём (или пройти сопряжение). Отправителя, которого WhatsApp адресует по LID, сопоставляют по номеру телефона, который Baileys передаёт вместе с ним, так что первый контакт без файла lid-mapping не отбрасывается. Если списка разрешённых отправителей нет, allowlist доверяет только списку JID групп и пропускает любого участника перечисленной группы, а open всё равно требует, чтобы участник был сопряжён, или WHATSAPP_ALLOW_ALL_USERS=true. По умолчанию бот отвечает на каждое пропущенное сообщение в группе; задайте require_mention: true / WHATSAPP_REQUIRE_MENTION=true, чтобы он отвечал только на @-упоминания, ответы боту или /commands (группы из free_response_chats — исключение).

Теперь запустите шлюз:

hermes gateway              # Foreground
hermes gateway install      # Install as a user service
sudo hermes gateway install --system   # Linux only: boot-time system service

Шлюз сам поднимет мост WhatsApp, используя сохранённую сессию.


Сохранение сессии

Свою сессию мост Baileys держит в ~/.hermes/platforms/whatsapp/session. Отсюда несколько следствий:

  • Сессии переживают перезапуски — заново сканировать QR-код каждый раз не придётся
  • В данных сессии лежат ключи шифрования и учётные данные устройства
  • Не публикуйте и не коммитьте этот каталог сессии — он даёт полный доступ к аккаунту WhatsApp

Повторная привязка

Если сессия сломалась (сброс телефона, обновление WhatsApp, ручная отвязка), в логах шлюза появятся ошибки соединения. Как это починить:

hermes whatsapp

Команда сгенерирует новый QR-код. Отсканируйте его заново — и сессия восстановится. С временными разрывами (моргнула сеть, телефон ненадолго ушёл в офлайн) шлюз справляется сам благодаря логике переподключения.


Голосовые сообщения

В WhatsApp у Hermes есть поддержка голоса:

  • Входящие: голосовые (.ogg opus) автоматически расшифровываются настроенным STT-провайдером: локальный faster-whisper, Groq Whisper (GROQ_API_KEY) или OpenAI Whisper (VOICE_TOOLS_OPENAI_KEY)
  • Исходящие: ответы TTS отправляются вложением — аудиофайлом MP3
  • По умолчанию ответы агента предваряются заголовком «☤ Hermes Agent». Его можно изменить или вовсе убрать в config.yaml:
# ~/.hermes/config.yaml
whatsapp:
  reply_prefix: ""                          # Empty string disables the header
  # reply_prefix: "🤖 *My Bot*\n──────\n"  # Custom prefix (supports \n for newlines)
  send_read_receipts: false                 # Mark accepted inbound messages as read (blue ticks)

Когда send_read_receipts установлен в true, адаптер помечает прошедшие политику входящие сообщения как прочитанные после фильтрации по DM/группе/упоминанию. Отклонённые сообщения (например, от отправителей не из allowlist) прочитанными не помечаются. По умолчанию отключено — ради приватности. При изменении этой настройки подпроцесс моста автоматически перезапускается при следующем подключении.


Форматирование и доставка сообщений

WhatsApp умеет показывать потоковые (постепенные) ответы — бот правит своё сообщение в реальном времени по мере того, как ИИ генерирует текст, ровно как в Discord и Telegram. Внутри по возможностям доставки WhatsApp отнесён к платформам уровня TIER_MEDIUM.

Разбивка на части

Длинные ответы автоматически режутся на несколько сообщений по 4096 символов на фрагмент (практический предел отображения в WhatsApp). Настраивать ничего не нужно — шлюз сам делит текст и отправляет фрагменты по очереди.

Markdown под WhatsApp

Обычный Markdown в ответах ИИ автоматически переводится в родное форматирование WhatsApp:

MarkdownWhatsAppКак отображается
**bold***bold*жирный
~~strikethrough~~~strikethrough~зачёркнутый
# Heading*Heading*Жирный текст (родных заголовков нет)
[link text](url)link text (url)URL в тексте

Блоки кода и инлайн-код остаются как есть: тройные бэктики WhatsApp поддерживает изначально.

Прогресс по инструментам

Когда агент вызывает инструменты (веб-поиск, операции с файлами и прочее), WhatsApp показывает живые индикаторы прогресса: видно, какой инструмент сейчас работает. Это включено по умолчанию — настраивать ничего не надо.

Нативные опросы, clarify-как-опрос и геолокация

Адаптер на Baileys-мосту (режим бота) поддерживает несколько нативных типов сообщений WhatsApp:

  • Опросы — агент может отправить нативный опрос WhatsApp (вопрос + варианты) через эндпоинт /send-poll моста. Голоса за варианты возвращаются обратно в разговор.
  • Уточняющие вопросы как опросы — когда агент задаёт clarify-вопрос с несколькими вариантами ответа, он отображается как нативный опрос с одиночным выбором; тап по варианту отвечает на вопрос. Если отправить опрос не удалось, адаптер откатывается к обычному текстовому вопросу. Запросы подтверждения (approval) никогда не превращаются в опросы — опросы используются только для настоящих clarify-вопросов с выбором из нескольких вариантов.
  • Геометки — агент может отправить нативную геометку (широта/долгота, опционально название/адрес) через /send-location, а входящие отправленные локации (в том числе live-локации) доставляются агенту как сообщения с геолокацией.

Всё это работает «из коробки» в режиме бота (Baileys); настраивать ничего не нужно.

Пакетирование сообщений (debounce)

Каждое сообщение WhatsApp доставляет по отдельности, поэтому быстрая серия (пересланные подборки, разбитая на части вставка, многострочный текст) иначе порождала бы по отдельному вызову агента на каждый фрагмент — а это лишний расход токенов и несколько разрозненных ответов. Адаптер копит идущие подряд текстовые сообщения из одного чата и отправляет их единым запросом после короткой паузы (по умолчанию 0,3 с, до 1 с для очень длинных фрагментов; потолок — 2 с / 4 с). Подстраивается через config.yaml:

# ~/.hermes/config.yaml
gateway:
  platforms:
    whatsapp:
      extra:
        text_batch_delay_seconds: 0.3         # quiet period before flushing a batch (max 2.0)
        text_batch_split_delay_seconds: 1.0   # extended delay near the split threshold (max 4.0)

Поставьте text_batch_delay_seconds: 0, чтобы каждое сообщение уходило сразу (пакетирование отключится).

Ответы с цитатой

Ответ на более раннее сообщение (цитата) даёт агенту процитированный текст как контекст. Цитата изображения, голосового, видео или документа вдобавок прикрепляет этот файл к ходу, так что «что это?» под процитированной картинкой работает — независимо от того, пришло вложение от другого человека или от самого бота (график из cron, сгенерированная картинка). С цитатой WhatsApp присылает лишь заглушку-миниатюру, поэтому файл берётся из кэша скачиваний моста (входящие медиа, в памяти на время жизни моста) или из локального индекса собственных отправок бота (последние 1000 сообщений); цитаты чего-то более старого приходят без вложения.


Устранение неполадок

ПроблемаРешение
QR-код не сканируетсяПроверьте ширину терминала (от 60 столбцов). Попробуйте другой терминал. Убедитесь, что сканируете из нужного аккаунта WhatsApp (номер бота, а не личный).
QR-код истекаетКоды обновляются примерно раз в 20 секунд. Если время вышло, перезапустите hermes whatsapp.
Сессия не сохраняетсяПроверьте, что каталог ~/.hermes/platforms/whatsapp/session существует и доступен на запись. В контейнере подключите его как постоянный том.
Неожиданный выход из аккаунтаПосле долгого простоя WhatsApp отвязывает устройства. Держите телефон включённым и в сети, при необходимости заново привяжитесь через hermes whatsapp.
Мост падает или зацикливается на переподключенииПерезапустите шлюз, обновите Hermes и выполните повторную привязку, если сессию обнулило изменение протокола WhatsApp.
Бот перестал работать после обновления WhatsAppОбновите Hermes до свежей версии моста, затем привяжитесь заново.
macOS: «Node.js not installed», хотя node работает в терминалеСлужбы launchd не наследуют PATH вашей оболочки. Выполните hermes gateway install, чтобы перезаписать текущий PATH в plist, а затем hermes gateway start. Подробности — в документации по службе шлюза.
Сообщения не доходятУбедитесь, что WHATSAPP_ALLOWED_USERS содержит номер отправителя (с кодом страны, без + и пробелов), либо задайте *, чтобы пропускать всех. Поставьте WHATSAPP_DEBUG=true в .env и перезапустите шлюз — тогда сырые события сообщений будут видны в bridge.log.
Бот отвечает незнакомцам кодом привязкиЗадайте whatsapp.unauthorized_dm_behavior: ignore в ~/.hermes/config.yaml, если хотите, чтобы неавторизованные личные сообщения молча игнорировались.

Безопасность

внимание

Настройте контроль доступа до запуска в бой. Пропишите в WHATSAPP_ALLOWED_USERS конкретные номера телефонов (с кодом страны, без +), поставьте *, чтобы пропускать всех, либо задайте WHATSAPP_ALLOW_ALL_USERS=true. Без любой из этих настроек шлюз отклоняет все входящие сообщения — в целях безопасности.

По умолчанию неавторизованные личные сообщения всё же получают в ответ код привязки. Если хотите, чтобы приватный номер WhatsApp полностью молчал для незнакомцев, задайте:

whatsapp:
  unauthorized_dm_behavior: ignore
  • В каталоге ~/.hermes/platforms/whatsapp/session лежат полные учётные данные сессии — берегите его как пароль
  • Выставьте права на файлы: chmod 700 ~/.hermes/platforms/whatsapp/session
  • Используйте отдельный номер телефона под бота, чтобы изолировать риск от личного аккаунта
  • Заподозрили компрометацию — отвяжите устройство через WhatsApp → Настройки → Связанные устройства
  • Номера телефонов в логах частично затираются, но всё же пересмотрите свою политику хранения логов
перевод и редактура:

обновлено

⌕ ESC