БэкендPython

Как отправить сообщение в Discord через webhook на Python: без бота

Создаём Discord webhook и отправляем сообщение из Python через HTTP POST: JSON, переменная окружения, wait=true, обработка ошибок и защита URL.

Кодик

Автор

6 мин чтения

Discord webhook отправляет сообщение в канал одним HTTP POST и не требует отдельного бота. Скрипт возьмёт секретный URL из переменной окружения, отправит JSON и дождётся подтверждения. В коде не будет токена, который легко случайно загрузить на GitHub.

Webhook подходит для уведомлений о сборке, резервной копии, новом заказе или результате учебного скрипта. У него узкая задача: принимать данные по выданному URL и публиковать их в выбранный канал. Команды, чтение переписки и сложная логика уже требуют приложения или бота. Именно поэтому webhook проще для первого знакомства с HTTP, но обращаться с его URL нужно как с паролем.

1Создаём

В настройках сервера выбираем канал и копируем Webhook URL.

2Отправляем

Python кодирует content в JSON и делает POST с Content-Type.

3Проверяем

wait=true возвращает созданное сообщение, а ошибки не теряются.

Что такое webhook и где взять URL

Откройте настройки своего Discord-сервера, затем Integrations и Webhooks. Создайте webhook, выберите текстовый канал и скопируйте URL. Для этого нужны права управления webhook. Ссылка уже содержит идентификатор и токен, поэтому дополнительный логин в Python не нужен. Добавлять её прямо в файл нельзя: код будем читать значение из DISCORD_WEBHOOK_URL.

ЧастьГде находитсяЗачем нужна
Webhook URLПеременная окруженияАдрес и секрет доступа
contentJSON-телоТекст сообщения до 2000 символов
allowed_mentionsJSON-телоЗапрещает неожиданные упоминания
wait=trueСтрока запросаВозвращает созданное сообщение

Webhook URL заменяет авторизацию для этого узкого действия. Тот, кто получил ссылку, сможет писать в канал от имени webhook, поэтому URL нельзя показывать в статье, скриншоте, логе или репозитории.

Путь одного уведомления
От Python до выбранного канала. wait=true возвращает подтверждение

Сначала соберите минимальный сценарий. Не добавляйте оформление, хранение и дополнительные режимы одновременно. Добейтесь одного правильного прохода от входных данных до результата, затем проверяйте каждое улучшение отдельно.

Отправляем сообщение стандартной библиотекой Python

Пример использует только стандартную библиотеку. Он добавляет wait=true, ограничивает длину content и передаёт allowed_mentions с пустым parse, чтобы строка из внешнего источника неожиданно не позвала @everyone. Перед запуском задайте переменную окружения в своём терминале, не вставляя реальный URL в файл.

import json
import os
from urllib.error import HTTPError, URLError
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from urllib.request import Request, urlopen


def add_wait_parameter(url: str) -> str:
    parts = urlsplit(url)
    query = dict(parse_qsl(parts.query, keep_blank_values=True))
    query["wait"] = "true"
    return urlunsplit((
        parts.scheme,
        parts.netloc,
        parts.path,
        urlencode(query),
        parts.fragment,
    ))


def send_message(webhook_url: str, text: str) -> dict:
    clean_text = text.strip()
    if not clean_text:
        raise ValueError("Сообщение не может быть пустым")
    if len(clean_text) > 2000:
        raise ValueError("Discord принимает до 2000 символов в content")

    payload = {
        "content": clean_text,
        "allowed_mentions": {"parse": []},
    }
    data = json.dumps(payload).encode("utf-8")
    request = Request(
        add_wait_parameter(webhook_url),
        data=data,
        headers={
            "Content-Type": "application/json",
            "User-Agent": "codik-webhook-example/1.0",
        },
        method="POST",
    )

    with urlopen(request, timeout=10) as response:
        return json.load(response)


def main() -> None:
    webhook_url = os.environ.get("DISCORD_WEBHOOK_URL")
    if not webhook_url:
        raise SystemExit("Сначала задайте DISCORD_WEBHOOK_URL")

    try:
        message = send_message(
            webhook_url,
            "Сборка завершена. Проверка webhook из Python прошла.",
        )
    except HTTPError as error:
        raise SystemExit(f"Discord вернул HTTP {error.code}") from error
    except URLError as error:
        raise SystemExit(f"Нет соединения: {error.reason}") from error

    print(f"Сообщение отправлено, id: {message['id']}")


if __name__ == "__main__":
    main()
Что должно работать после первого запуска

В исходнике нет реального Webhook URL. POST содержит JSON и правильный Content-Type. wait=true заставляет Discord вернуть объект сообщения. HTTP-ошибка и отсутствие сети дают разные понятные сообщения.

Webhook или бот
Выбирайте по задаче, а не по моде. Сравнение показывает, какой вариант легче поддерживать и проверять.

Почему URL webhook считается секретом

У входящего webhook есть URL с токеном. При выполнении endpoint Discord ждёт хотя бы content, embeds, components, файл или poll. Без параметра wait успешная отправка обычно отвечает 204 No Content. С wait=true сервер ждёт подтверждения и возвращает созданное сообщение, поэтому скрипт может прочитать его id. Это удобнее для первого теста: успех подтверждается ответом API, а не только появлением текста в приложении.

СитуацияОжидаемый ответЧто проверить
Успех с wait=true200 и JSON сообщенияТекст и id
Неверный или удалённый URL401 или 404Пересоздать webhook
Слишком длинный contentПроверка до запросаНе больше 2000 символов
Слишком много запросов429Не повторять запрос в тесном цикле

Никогда не публикуйте настоящий URL webhook. Переменная окружения защищает только от случайного попадания в код, но не делает секрет неуязвимым: не печатайте её, не добавляйте в traceback и не показывайте терминал на стриме. Если ссылка утекла, удалите webhook или смените токен в настройках Discord. Для текста из формы оставляйте allowed_mentions: {"parse": []}, иначе строка пользователя может вызвать массовое упоминание.

Смотрите на состояние, а не только на красивый экран. Перезапуск, повторный клик, пустой ввод, медленный ответ и позднее подключение часто открывают ошибки, которых не видно в идеальном демонстрационном проходе.

Практика: проверяем ответ и типичные ошибки

Создайте отдельный тестовый канал и webhook. Сначала отправьте короткую нейтральную строку. Затем проверьте пустое сообщение, отключённую сеть и временно испорченный конец URL. Не устраивайте цикл из десятков запросов: API применяет ограничения частоты, а уведомление должно отправляться по событию, а не непрерывно. После теста удалите сообщение вручную, если оно больше не нужно.

Проверка своими руками
  1. Создайте тестовый webhook в Server Settings → Integrations → Webhooks.
  2. Задайте DISCORD_WEBHOOK_URL в текущем терминале, не добавляя ссылку в файл.
  3. Запустите python send_discord.py и найдите сообщение в выбранном канале.
  4. Сверьте напечатанный id с данными ответа, не выводя сам URL.
  5. Испытайте пустую строку через вызов send_message и получите ValueError до сети.
  6. Удалите или поверните webhook после теста, если URL мог попасть в запись экрана.
Готово, если выполняются все пункты

Сообщение появляется только в выбранном канале. Секрет отсутствует в исходнике и истории Git. Скрипт различает HTTP-ошибку и сетевую ошибку. Внешний текст не создаёт упоминания автоматически.

Четыре проверки секрета
До первого реального уведомления. Пройдите все четыре сценария перед публикацией проекта.

Доводим уведомление до надёжного скрипта

Следующий шаг: вынести текст в аргумент командной строки или вызывать send_message после реального события, например завершения резервной копии. Не превращайте webhook в универсального бота. Если нужно читать сообщения, отвечать на команды, работать с ролями или хранить состояние пользователей, используйте Discord-приложение и библиотеку для бота. У webhook другой плюс: маленькая поверхность и один понятный POST.

URL записан в Python-файле

Секрет легко уйдёт в GitHub или скриншот. Читайте его из окружения.

Запрос без Content-Type

Discord ожидает корректно закодированный JSON.

Успех определяется только по print

Добавьте wait=true и читайте ответ API.

Пользовательский текст разрешает mentions

Передавайте allowed_mentions и не включайте массовые упоминания без необходимости.

Сверьтесь с первичным источником. Поведение функций и ограничения примера проверяйте по официальной документацией Discord по Execute Webhook. Документация особенно важна, когда меняются API, права или правила платформы.

Что получится в итоге

Готовый результат проекта: Как отправить сообщение в Discord через webhook на Python: без бота
Готовый скрипт отправляет одно безопасное уведомление, получает id сообщения и не хранит секретный URL в исходнике.

Короткие ответы
Нужен ли бот-токен?

Нет. Входящий webhook использует собственный URL с токеном.

Зачем wait=true?

Чтобы Discord дождался сохранения и вернул JSON созданного сообщения.

Что делать, если URL попал в GitHub?

Сразу удалить или повернуть webhook в настройках Discord, затем очистить секрет из истории репозитория.

Как понять, что проект готов?

Повторите основной сценарий несколько раз, затем проверьте пустой ввод, граничные значения и повторный запуск. Ошибка должна объяснять, что исправить, а не закрывать программу.

Можно ли скопировать код целиком?

Можно использовать его как стартовую точку. После первого запуска измените хотя бы один параметр и объясните себе, где хранятся данные, где выполняется действие и где появляется результат.

Начните с одного POST, затем решайте, нужен ли бот

Функции, JSON, исключения и HTTP можно закрепить в курсе Python. Для полноценного приложения откройте Discord-бота на Python.

Если хотите разобрать запросы со стороны браузера, изучите Fetch API. Серверные запросы подробнее разобраны в статье про API и requests. В Кодике удобно сначала протестировать функцию с подменённым HTTP-ответом.