Вебхук Discord - это URL, который превращает HTTP POST в сообщение в одном канале. Ни бота, ни токена, ни подключения к шлюзу, ни входа в систему: вы создаёте URL в настройках канала, отправляете на него POST с JSON, и появляется сообщение. Для статуса сервера, оповещений о перезапуске, подтверждений бэкапа и сообщения «сервер Minecraft снова работает» это весь механизм, и настраивается он минут за четыре.
Стоит знать то, на чём потом ошибаются. Вебхук умеет только писать, поэтому всё, что должно читать чат или отвечать на команды, требует настоящего бота. Сообщение каждую минуту забивает канал шумом, который никто не читает; редактирование одного и того же сообщения даёт одну живую строку статуса. Discord ограничивает частоту вебхуков и возвращает 429 с точным числом секунд ожидания, которое большинство самодельных скриптов игнорирует. А сам URL - это учётные данные: любой, у кого он есть, может писать от имени вашего сервера, пока вы не удалите вебхук.
Что такое вебхук и чего он не умеет#
Вебхук принадлежит каналу, а не вам. У него есть id и токен, и вместе они образуют весь URL:
https://discord.com/api/webhooks/1234567890123456789/aBcD...tokenДругой аутентификации нет. Этот URL - учётные данные, и единственные. Любой, у кого он есть, может писать в этот канал от имени вебхука с любым именем и любым аватаром, пока кто-нибудь не удалит вебхук.
Что вебхук умеет:
- Отправлять сообщение в свой единственный канал:
content, embed-ы, файлы или всё вместе. - Переопределять отображаемое имя и аватар для каждого сообщения, так что один вебхук может писать как «Survival» и как «Lobby».
- Писать в тред этого канала через параметр запроса
thread_id. - Редактировать и удалять сообщения, которые он отправил раньше.
Чего он не умеет:
- Читать что-либо. Ни чат, ни реакции, ни список участников.
- Отвечать на слэш-команду, кнопку или упоминание.
- Писать куда-либо, кроме собственного канала.
- Назначать роли, выгонять, банить и делать что-либо из того, что делает администратор.
Этот список и есть всё решение. Односторонние уведомления - это вебхук. Всё двустороннее - это бот с токеном, подключением к шлюзу и настройкой intents, то есть другая программа с другими требованиями к хостингу: об этом говорится в статье хостинг Discord-бота 24/7, а о цене содержания онлайн-бота - в выборе тарифа для Discord-бота.
Создание и первое сообщение#
В Discord: правый клик по каналу, Edit Channel, Integrations, Webhooks, New Webhook. Назовите его по серверу, о котором он сообщает, а не «Webhook 1», потому что это имя по умолчанию показывается как автор сообщения. Скопируйте URL. В канале может быть до 15 вебхуков, чего с запасом хватает, чтобы у каждого сервера был свой.
Самое простое сообщение:
$ curl -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{"content": "Survival server is back up."}'Успешная отправка возвращает 204 No Content и пустое тело. Если нужно получить сообщение обратно - в частности его id, чтобы потом отредактировать, - добавьте ?wait=true, и вы получите 200 с полным объектом сообщения:
$ curl -X POST "$WEBHOOK_URL?wait=true" \ -H "Content-Type: application/json" \ -d '{"content": "Restarting for updates in 5 minutes."}'Два флага стоит выставлять почти в каждом автоматическом сообщении. allowed_mentions с пустым массивом parse не даёт строке лога, случайно содержащей @everyone, оповестить шестьсот человек - это классическая авария консольного моста. А flags: 4096 отключает предпросмотр ссылок, чтобы сообщение с URL не тащило в канал ненужную карточку.
{ "username": "Survival", "content": "Backup finished: 412 MB in 38s", "allowed_mentions": { "parse": [] }, "flags": 4096}Embed-ы: JSON, который делает сообщение читаемым#
Для однострочных оповещений хватает обычного content. Для панели статуса нужен embed: карточка с рамкой, цветной полосой, заголовком, полями в колонках и временной меткой. Структура фиксирована, а ограничения настоящие.
{ "username": "Valheim", "embeds": [ { "title": "Longship Crew", "description": "Online - world saved 4 minutes ago", "color": 3066993, "fields": [ { "name": "Players", "value": "3 / 10", "inline": true }, { "name": "Version", "value": "0.220.5", "inline": true }, { "name": "Uptime", "value": "6d 4h", "inline": true } ], "footer": { "text": "checked every 60s" }, "timestamp": "2026-09-21T14:03:00.000Z" } ], "allowed_mentions": { "parse": [] }}На чём попадаются:
- `color` - десятичное целое, а не шестнадцатеричная строка.
0x2ECC71- это3066993. Строка здесь даёт400. - `timestamp` должен быть в формате ISO 8601. Discord показывает его во времени каждого зрителя, и это лучшая причина использовать embed для сообщения статуса: никому не нужно вычислять ваш часовой пояс.
- `inline: true` ставит поля рядом, по три в ряд на десктопе и меньше на мобильных. Смесь inline и не-inline полей даёт раскладки, которых вы не задумывали, так что выберите что-то одно на секцию.
Жёсткие ограничения, при превышении которых возвращается 400, а не обрезка:
| Поле | Ограничение |
|---|---|
content | 2000 символов |
embeds в сообщении | 10 |
title | 256 символов |
description | 4096 символов |
fields | 25 |
name / value поля | 256 / 1024 символа |
footer.text | 2048 символов |
| Весь текст во всех embed-ах | 6000 символов |
Общий лимит в 6000 символов - тот, что кусает консольный мост. Длинный стек-трейс, вставленный в описание, тихо превышает его при обычной работе, а потом ломается именно во время инцидента, ради которого мост строили. Обрезайте до фиксированной длины со своей стороны, а полный текст кладите туда, где хранится полный текст, - см. логи, которые стоит хранить.
Есть два полезных сокращения для совместимости. Если дописать /slack к URL вебхука, он принимает нагрузку в формате Slack, а /github - в формате GitHub. Это значит, что инструмент, умеющий разговаривать только со Slack, - система мониторинга, CI-раннер - может писать в Discord вообще без адаптера. Направьте его на https://discord.com/api/webhooks/ID/TOKEN/slack, и он работает.
Одно сообщение с правками вместо сотни постов#
Устройство статусного вебхука по умолчанию - публиковать сообщение каждую минуту. Через сутки это 1440 сообщений, канал нечитаем, а у всех, у кого включены уведомления, он заглушён. Лучше устроить одно сообщение, которое редактируется на месте.
Опубликуйте один раз с ?wait=true, сохраните вернувшийся id, а дальше делайте PATCH:
# First run: create and remember the id$ MESSAGE_ID=$(curl -s -X POST "$WEBHOOK_URL?wait=true" \ -H "Content-Type: application/json" \ -d @status.json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")# Every run after that: edit the same message$ curl -s -X PATCH "$WEBHOOK_URL/messages/$MESSAGE_ID" \ -H "Content-Type: application/json" \ -d @status.jsonХраните id сообщения там, где он переживёт перезапуск, - хватит файла рядом со скриптом. Если id потерян или сообщение удалено, PATCH вернёт 404, и вы публикуете новое. Вот весь автомат состояний:
- Прочитайте сохранённый id сообщения. Если его нет, перейдите к шагу 4.
- Сделайте
PATCHсообщения с новым статусом. - Если ответ
404, забудьте id и перейдите к шагу 4. Иначе остановитесь. - Сделайте
POSTс?wait=true, сохраните вернувшийся id, остановитесь.
Отредактированное сообщение никого не оповещает, что как раз правильно для строки статуса, меняющейся каждую минуту. Заведите второй вебхук в другом канале для событий, которые должны оповещать: сервер упал, бэкап не удался, диск почти полон. Правка для состояния, публикация для событий - это различие, из-за которого канал не глушат.
Лимиты запросов, 429 и повтор, который нужно соблюдать#
Discord ограничивает частоту на каждом маршруте. Для вебхука практические цифры - примерно пять запросов за две секунды на вебхук и около тридцати сообщений в минуту в один канал. Эти числа не гарантированы, и Discord их меняет, так что не зашивайте их в виде паузы и не считайте дело сделанным. Обрабатывайте ответ.
При превышении лимита вы получаете 429 с телом JSON и заголовками:
HTTP/1.1 429 Too Many RequestsX-RateLimit-Limit: 5X-RateLimit-Remaining: 0X-RateLimit-Reset-After: 0.529Retry-After: 1{"message": "You are being rate limited.", "retry_after": 0.529, "global": false}retry_after в теле - это секунды в виде дробного числа, и именно оно авторитетно. Подождите столько и повторите один раз. Если global равно true, вы упёрлись в лимит на весь аккаунт - 50 запросов в секунду, - и у вас проблема серьёзнее, чем одно это сообщение.
Три правила, которые навсегда убережют вебхук от неприятностей:
- Никогда не повторяйте в плотном цикле. Скрипт, который повторяет сразу после
429, превращает задержку в полсекунды в непрерывный поток и может привести к отключению вебхука. - Группируйте. Десять строк лога в одном сообщении через переводы строк - это один запрос. Десять сообщений - десять. Консольный мост должен буферизовать секунду-две и отправлять пачку.
- Отступайте и при `5xx`. Под нагрузкой Discord возвращает
500и502. Экспоненциальная задержка с потолком, затем отказ и запись в локальный лог. Система мониторинга, которая падает из-за того, что её канал уведомлений занят, хуже, чем отсутствие мониторинга.
Откуда берётся статус: опрос игрового сервера#
Вебхук - лёгкая половина. Кто-то должен знать, сколько игроков онлайн, и этим кем-то служит запрос к самому игровому серверу.
| Семейство игр | Протокол | Где |
|---|---|---|
| Minecraft Java | Server List Ping, TCP | Игровой порт, обычно 25565 |
| Source и GoldSrc | A2S_INFO, UDP | Порт запросов |
| Valheim | Запрос Steam, UDP | Игровой порт плюс один, по умолчанию 2457 |
| Большинство игр на Unreal и Unity | A2S или REST-эндпоинт | По-разному, смотрите игру |
| Всё, где есть RCON | RCON, TCP | Порт RCON |
Библиотеки есть для всего этого, и писать собственный парсер A2S - плохое применение вечера. Важнее, где работает опросчик. Он не может осмысленно работать на игровом сервере, за которым следит, потому что самый интересный случай - когда игровой сервер лежит. Он должен быть отдельным небольшим процессом, работающим всегда.
Запланированная задача на тарифе игрового сервера выполняет команды консоли, бэкапы и действия с питанием - HTTP-запрос она сделать не может. Поэтому опросчику место там, где выполняется ваш собственный код: тариф приложений Node или Python, VDS или машина, которая у вас уже есть. На RE:NODE тарифы приложений начинаются с $4 в месяц и дают ту же панель, консоль и деплой из GitHub, чего хватает для скрипта в пятьдесят строк по таймеру. Актуальные тарифы смотрите на странице хостинга приложений, а насколько маленьким может быть «маленький» тариф - в статье выбор тарифа для Discord-бота.
Форма скрипта на любом языке одна и та же:
import json, os, urllib.requestdef post(payload, url=os.environ["WEBHOOK_URL"]): body = json.dumps(payload).encode() req = urllib.request.Request( url, data=body, headers={"Content-Type": "application/json"} ) with urllib.request.urlopen(req) as res: return res.statusОпрашивайте с фиксированным интервалом, сравнивайте результат с предыдущим, редактируйте сообщение статуса каждый раз и пишите в канал оповещений только когда состояние изменилось. Последнее условие и отличает полезный канал от заглушённого. Более подробный довод в пользу такого устройства есть в статье мониторинг, который что-то сообщает.
Консольные мосты, плагины и когда нужен настоящий бот#
Большинство людей ничего из описанного не пишут, потому что плагин уже существует.
- Minecraft. DiscordSRV - устоявшийся мост чата. Для основной функции он использует токен бота, а не вебхук - потому что ему нужно читать сообщения Discord и переносить их в игровой чат, чего вебхук не умеет, - а внутри использует вебхуки, чтобы игроки в игре появлялись со своими именами и скинами. Из-за такого гибрида нужно и то, и другое.
- FiveM. Ресурсы, отправляющие в вебхук входы, выходы, баны и действия админов, - стандарт, и txAdmin умеет сообщать в Discord. Настройка описана в статье FiveM и txAdmin.
- Игры на Source. Плагины SourceMod отправляют в вебхук результаты матчей и действия админов.
- Всё остальное. Следите за хвостом файла лога и отправляйте строки, подходящие под шаблон. Это универсальный запасной вариант, он работает для любой игры, и это около двадцати строк кода.
Будьте избирательны с мостом. Полный вывод консоли в Discord нечитаем уже через день и непрерывно расходует лимит запросов. Выбирайте события, из-за которых вы бы встали: сервер остановился, строка о сбое, число игроков пересекло порог, бэкап не удался. Если ваш сервер перезапускается достаточно часто, чтобы оповещения шумели, проблема не в оповещении, а в перезапусках - о них статья почему ваш игровой сервер постоянно перезапускается.
Как только вам понадобится что-либо из следующего, нужен бот, а не вебхук: команда /status, кнопка перезапуска сервера, чтение чата Discord в игру, назначение роли, когда кто-то привязывает аккаунт, или реакция на сообщение. Это настоящее приложение с токеном, который надо беречь, и процессом, который надо держать живым.
Относитесь к URL как к паролю и отправляйте оповещения, которые стоит читать#
URL вебхука - это предъявительские учётные данные без срока действия и без области действия за пределами канала. Обращайтесь с ним соответственно.
- Никогда не коммитьте его. Положите в переменную окружения, читайте при запуске и держите вне репозитория. Для этого на вкладке Startup панели есть переменные окружения. Общий случай разобран в статье переменные окружения и секреты.
- Никогда не вставляйте его в сообщение Discord, скриншот, pastebin или тикет в поддержку. Если кому-то нужно посмотреть ваш конфиг, закройте часть URL с токеном.
- Ротация - это удаление. Сброса пароля у вебхука нет. Если он утёк, удалите его в настройках канала и создайте новый; старый URL перестаёт работать сразу.
- Один вебхук на одну цель. Отдельные вебхуки для статуса, оповещений и консольного моста означают, что утечка или ошибка затрагивает один канал, а по шуму сразу видно, какой скрипт слишком разговорчив.
Затем будьте беспощадны в том, что отправляете. Отказ оповещений - это не пропущенное оповещение, а такое их количество, что люди перестают смотреть. Канал, который пишет раз в неделю, читают. Канал, который пишет каждые десять минут, глушат за две недели, и заглушён он будет в ту ночь, когда что-то действительно сломается.
Хороший набор по умолчанию для игрового сервера: сервер неожиданно остановился, сервер вернулся, бэкап не удался, диск заполнен более чем на 85 процентов, число игроков пересекло важный для вас порог и применено обновление. Это шесть типов событий, большинство из которых срабатывают редко. Всё остальное может жить в редактируемом сообщении статуса, где оно меняется беззвучно, или в логе, куда можно зайти и прочитать, когда появится повод.
FAQ#
Нужен ли токен бота, чтобы пользоваться вебхуком?
Нет. URL вебхука содержит собственный токен, и больше ничего не требуется. Токен бота нужен только тогда, когда что-то должно читать из Discord: двусторонние мосты чата, слэш-команды, кнопки или управление ролями.
Как часто можно писать в вебхук?
Примерно пять запросов за две секунды на вебхук и около тридцати сообщений в минуту в канал, но считайте это ориентиром. Надёжный подход - обрабатывать ответ 429 и ждать значение retry_after, которое он даёт.
Можно ли обновить сообщение вместо публикации нового?
Да. Опубликуйте с ?wait=true, сохраните id сообщения из ответа, затем сделайте PATCH на URL вебхука с добавленным /messages/{id}. Правка никого не оповещает, что идеально для живой строки статуса.
Почему мой вебхук пингует всех?
Потому что тело сообщения содержало @everyone или упоминание роли, и ничто это не подавило. Отправляйте "allowed_mentions": {"parse": []} в каждом автоматическом сообщении, особенно в тех, что пересылают текст чата или лога.
Где должен работать опросчик статуса?
Не на том сервере, за которым он следит, потому что он нужен вам работающим, когда этот сервер лежит. Подойдёт небольшой тариф приложений, VDS или любая машина, работающая постоянно. Запланированная задача на тарифе игрового сервера может выполнять команды консоли и бэкапы, но HTTP-запрос сделать не может.
Мой вебхук перестал работать. Что случилось?
Либо его удалили в настройках канала, либо удалили канал, либо URL был пересоздан. Срока действия и постоянного бана по лимиту нет. Проверьте, что вебхук ещё существует, затем проверьте, что вы отправляете весь URL целиком вместе с токеном.




Комментарии
Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.