Discord Webhook через discord.py (полный гайд по discord.Webhook)
Отправка, edit, delete и стрим файлов через Discord webhook на discord.py. Async с aiohttp: embed'ы, треды, файлы, view-компоненты.
Если стек уже на 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 loopdiscord.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 IDdiscord.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.py docs: Webhook
- discord.py docs: Embed
- Discord developer docs: Webhook Resource
Попробуйте в нашем инструменте
Открыть конструктор Discord Webhook