Перейти к содержимому
Все статьи
· Обновлено 25 апреля 2026 г.

Discord Webhook через discord.py (полный гайд по discord.Webhook)

Отправка, edit, delete и стрим файлов через Discord webhook на discord.py. Async с aiohttp: embed'ы, треды, файлы, view-компоненты.

discord.pywebhookpythonasyncioaiohttpтуториалdiscord apiasync
Discord Webhook через discord.py (полный гайд по discord.Webhook)

Если стек уже на discord.py — для webhook’ов не нужно переключаться на raw requests. Библиотека предлагает discord.Webhook, который встраивается в тот же async event loop, использует те же embed-объекты и file-хелперы, что и бот. Bot-токен не нужен — достаточно webhook URL.

В гайде — полное прохождение для [email protected] (актуальная ветка на 2026): отправка, edit, delete, файлы, треды и async-паттерны.

Установка

pip install discord.py

discord.Webhook — часть core, без extras.

Sync vs Async — выбираем один

discord.py даёт два адаптера:

  • discord.Webhook (async, default) — на aiohttp, работает в event loop
  • discord.SyncWebhook (sync) — на requests, блокирующий

Async — если вы внутри async def, делаете бота, или у вас asyncio-приложение. Sync — для коротких скриптов и одноразовых CI-крючков.

Минимальная отправка (async)

import asyncio
import aiohttp
import discord

WEBHOOK_URL = "https://discord.com/api/webhooks/ID/TOKEN"

async def main():
    async with aiohttp.ClientSession() as session:
        webhook = discord.Webhook.from_url(WEBHOOK_URL, session=session)
        await webhook.send("Hello from discord.py!")

asyncio.run(main())

Всегда передавайте session — webhook’и переиспользуют коннект; новая сессия на каждый вызов = задержка.

Минимальная отправка (sync)

import discord

webhook = discord.SyncWebhook.from_url(
    "https://discord.com/api/webhooks/ID/TOKEN"
)
webhook.send("Hello from sync discord.py!")

Тот же API, без await и event loop. Для notify.py, который запускают разово.

Embed’ы

import discord

embed = discord.Embed(
    title="Деплой завершён",
    description="Все сервисы здоровы",
    color=0x57f287,
    timestamp=discord.utils.utcnow(),
)
embed.add_field(name="Длительность", value="2м 14с", inline=True)
embed.add_field(name="Commit", value="abc1234", inline=True)
embed.set_footer(text="CI bot")

await webhook.send(embed=embed)
# Несколько:
await webhook.send(embeds=[embed1, embed2])

discord.Embed валидирует лимиты при добавлении — add_field со значением 1025 символов кидает ValueError сразу.

Имя и аватар

await webhook.send(
    content="CI build failed",
    username="CI Reporter",
    avatar_url="https://example.com/ci-avatar.png",
)

Override default’ов webhook’а на одно сообщение.

Edit и Delete

Чтобы получить сообщение для редактирования — wait=True:

msg = await webhook.send("Работаем…", wait=True)

# Edit
await msg.edit(content="Готово")

# Delete
await msg.delete()

Без wait=True send возвращает None. Объект WebhookMessage имеет .edit() и .delete(), которые таргетят оригинал.

Если ID сохранили и нужно обработать позже (между перезапусками):

msg = await webhook.fetch_message(message_id=123456789012345678)
await msg.edit(content="Обновлено после рестарта")

Файлы

discord.File принимает path, file-like объект или BytesIO:

import io
import discord

# Из пути
file1 = discord.File("./report.pdf", filename="report.pdf")

# Из bytes
buf = io.BytesIO(b"plain text content")
file2 = discord.File(buf, filename="note.txt")

await webhook.send(content="Прикреплены отчёты", files=[file1, file2])

Для файлов под 25 МБ — стримить с диска (open с "rb"), не грузить в память.

Embed с прикреплённой картинкой

Ссылка на файл внутри embed’а через attachment://:

chart = discord.File("./chart.png", filename="chart.png")

embed = discord.Embed(title="Q4 Revenue")
embed.set_image(url="attachment://chart.png")

await webhook.send(file=chart, embed=embed)

Имя в attachment://chart.png должно точно совпадать с filename=.

Треды и forum-каналы

Для тредов передавайте thread:

await webhook.send(
    "Postим в тред",
    thread=discord.Object(id=987654321098765432),
)

В forum-каналах новый пост через thread_name:

await webhook.send(
    content="Тело нового forum-поста",
    thread_name="Daily build",
)

Edit/delete внутри тредов — тот же thread:

msg = await webhook.send("text", thread=discord.Object(id=tid), wait=True)
await msg.edit(content="updated")  # сам понимает, в каком треде сообщение

Components — кнопки, select

Webhook’и могут прикреплять View:

import discord

class ApprovalView(discord.ui.View):
    @discord.ui.button(label="Open Builder",
                       style=discord.ButtonStyle.link,
                       url="https://discord-webhook.com/app")
    async def open_builder(self, interaction, button):
        pass  # link-кнопки не вызывают callback

view = ApprovalView()
await webhook.send(content="Деплой готов", view=view)

Замечание: кнопки с customId и callback требуют подключённого бота для обработки interaction. Webhook-only sender’ы — только ButtonStyle.link (без callback) или в паре с ботом, слушающим interaction_create.

Rate limits — встроенная обработка

Webhook.send ходит через HTTPClient discord.py, который:

  • Трекает X-RateLimit-Remaining, ждёт при 0
  • Уважает Retry-After из 429 с auto-retry
  • Backoff на 5xx с ограниченным числом ретраев

Для высокой нагрузки (>30/мин на один webhook) сериализуйте через очередь или asyncio.Semaphore:

import asyncio

sem = asyncio.Semaphore(1)  # один in-flight за раз

async def queued_send(content):
    async with sem:
        await webhook.send(content)

См. гайд по rate limits для token-bucket.

Типизация (mypy / pyright)

Пакет идёт со stub’ами. Со строгой проверкой:

from typing import Optional
import aiohttp
import discord

async def notify(session: aiohttp.ClientSession, message: str) -> Optional[discord.WebhookMessage]:
    webhook = discord.Webhook.from_url(WEBHOOK_URL, session=session)
    return await webhook.send(message, wait=True)

WebhookMessage — тип возврата при wait=True; иначе None. Pyright/mypy это проверяют.

Обработка ошибок

Ошибки — discord.HTTPException и его наследники:

import discord

try:
    await webhook.send(content="x" * 2001)
except discord.HTTPException as e:
    # e.status — HTTP-статус (400, 404, …)
    # e.code — код Discord (50035 = Invalid Form Body)
    # e.text — полный текст
    print(f"HTTP {e.status} code={e.code} {e.text}")

Частые подклассы:

  • discord.NotFound (404) — webhook удалён, неверный message ID
  • discord.Forbidden (403) — изменились права канала
  • discord.RateLimited — поднимается только если вы выключили auto-wait; default — молча ждёт

Паттерн: одна сессия, много webhook’ов

Reuse одной aiohttp.ClientSession = пул коннектов:

import asyncio
import aiohttp
import discord

WEBHOOKS = {
    "alerts": "https://discord.com/api/webhooks/A/X",
    "deploys": "https://discord.com/api/webhooks/B/Y",
    "audit": "https://discord.com/api/webhooks/C/Z",
}

async def main():
    async with aiohttp.ClientSession() as session:
        hooks = {
            name: discord.Webhook.from_url(url, session=session)
            for name, url in WEBHOOKS.items()
        }
        await asyncio.gather(
            hooks["alerts"].send("Алерт: высокий CPU"),
            hooks["deploys"].send("Деплой стартовал"),
            hooks["audit"].send("Audit log запись"),
        )

Три вызова — один TLS handshake.

Попробуйте вживую

Соберите payload визуально в конструкторе Discord Webhook и переведите в discord.Embed по полям. Для Python на requestsгайд через requests; для других языков — discord.js и PHP.

Источники

Попробуйте в нашем инструменте

Открыть конструктор Discord Webhook