Discord Webhook через discord.js (полный гайд по WebhookClient)
Отправка, редактирование, удаление и стриминг файлов через Discord webhook на discord.js WebhookClient. TypeScript, embed'ы, треды, компоненты и rate limit.
Если уже используете discord.js для бота, в библиотеке есть WebhookClient, который сам делает всё что нужно: знает про rate limits, ретраит, embed builder, file streams. Bot-токен не нужен — достаточно webhook URL.
В гайде — полный жизненный цикл webhook’а на [email protected] (актуальный LTS на 2026): отправка, edit, delete, файлы, треды и подводные камни, на которые ловятся те, кто пришёл с raw fetch.
Установка
npm install discord.js
WebhookClient встроен — отдельный пакет не нужен.
Минимальная отправка
import { WebhookClient } from 'discord.js';
const webhook = new WebhookClient({ url: process.env.WEBHOOK_URL });
await webhook.send({ content: 'Hello from discord.js!' });
Вот и всё. WebhookClient парсит URL, вынимает ID и токен, делает POST. Rate limits — прозрачно: при 429 ждёт и ретраит сам.
Из ID + токена
Если они отдельно:
const webhook = new WebhookClient({
id: '123456789012345678',
token: 'abc123-secret-token-xyz',
});
Embed’ы — EmbedBuilder
import { WebhookClient, EmbedBuilder } from 'discord.js';
const webhook = new WebhookClient({ url: process.env.WEBHOOK_URL });
const embed = new EmbedBuilder()
.setTitle('Деплой завершён')
.setDescription('Все сервисы здоровы')
.setColor(0x57f287)
.setTimestamp()
.addFields(
{ name: 'Длительность', value: '2м 14с', inline: true },
{ name: 'Commit', value: 'abc1234', inline: true },
)
.setFooter({ text: 'CI bot' });
await webhook.send({ embeds: [embed] });
EmbedBuilder валидирует лимиты на этапе сборки — setTitle длиннее 256 символов кидает синхронно, не ждёт 400 от Discord.
Несколько embed’ов
await webhook.send({
embeds: [
new EmbedBuilder().setTitle('Service A').setColor(0x57f287),
new EmbedBuilder().setTitle('Service B').setColor(0xed4245),
],
});
До 10 embed’ов на сообщение; общий текст всех ≤ 6000 символов.
Edit и Delete
const message = await webhook.send({ content: 'Работаем…' });
const messageId = message.id;
// Подождали и обновили
await new Promise(r => setTimeout(r, 5000));
await webhook.editMessage(messageId, { content: 'Готово' });
// Потом
await webhook.deleteMessage(messageId);
webhook.send всегда возвращает объект сообщения — никаких ?wait=true query вручную как с raw fetch. Библиотека делает это сама.
Кастомное имя и аватар
await webhook.send({
content: 'CI build failed',
username: 'CI Reporter',
avatarURL: 'https://example.com/ci-avatar.png',
});
Override на одно сообщение; default из настроек webhook’а используется, если не указали.
Файлы — Buffer, Stream, Path
discord.js принимает файлы тремя способами:
import fs from 'node:fs';
import { AttachmentBuilder } from 'discord.js';
// 1. Из локального пути
const file1 = new AttachmentBuilder('./report.pdf', { name: 'report.pdf' });
// 2. Из Buffer
const buf = Buffer.from('обычный текст');
const file2 = new AttachmentBuilder(buf, { name: 'note.txt' });
// 3. Из стрима
const stream = fs.createReadStream('./large.log');
const file3 = new AttachmentBuilder(stream, { name: 'large.log' });
await webhook.send({
content: 'Прикреплены отчёты',
files: [file1, file2, file3],
});
Для файлов под 25 МБ — стримы (не грузят файл в память целиком).
Embed с прикреплённой картинкой
Чтобы файл попал в embed, имя + ссылка через attachment://:
const chart = new AttachmentBuilder('./chart.png', { name: 'chart.png' });
const embed = new EmbedBuilder()
.setTitle('Q4 Revenue')
.setImage('attachment://chart.png');
await webhook.send({ embeds: [embed], files: [chart] });
Отправка в тред
В тредах (или forum-постах) — threadId:
await webhook.send({
content: 'Message into thread',
threadId: '987654321098765432',
});
В forum-каналах можно создать новый пост через threadName:
await webhook.send({
content: 'New thread body',
threadName: 'Daily build', // создаёт новый forum-пост
});
Edit/delete внутри тредов требуют того же threadId:
await webhook.editMessage(messageId, { content: 'updated' }, { threadId });
Components — кнопки и select menu
Webhook может прикреплять интерактивные компоненты:
import {
WebhookClient,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} from 'discord.js';
const webhook = new WebhookClient({ url: process.env.WEBHOOK_URL });
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setURL('https://discord-webhook.com/app')
.setLabel('Open Builder')
.setStyle(ButtonStyle.Link),
new ButtonBuilder()
.setCustomId('approve')
.setLabel('Approve')
.setStyle(ButtonStyle.Success),
);
await webhook.send({
content: 'Новый деплой готов к approval',
components: [row],
});
Замечание: webhook может отправить кнопки с
customId, но не может обработать клик — для этого нужен бот с listener’ом interaction. Webhook нормально работает сLink-кнопками (без callback), либо в паре с ботом.
Rate Limits — как обрабатывает discord.js
WebhookClient использует общий REST-менеджер библиотеки:
- Трекает
X-RateLimit-Remainingper bucket - Очередь следующих запросов при remaining = 0
- Уважает
Retry-Afterиз 429 с auto-retry - Backoff на 5xx с экспонентой
Свой retry-loop писать обычно не нужно — webhook.send() в плотном цикле сериализуется библиотекой. Глобальные настройки:
import { REST } from 'discord.js';
const rest = new REST({
rejectOnRateLimit: ['/channels'], // кидать вместо ожидания
retries: 3,
});
В большинстве сценариев default’ы ок. Если шлёте > 30 сообщений/мин на webhook — см. гайд по rate limits для queue-стратегий.
TypeScript
В пакете есть .d.ts — WebhookClient полностью типизирован:
import { WebhookClient, type WebhookMessageCreateOptions } from 'discord.js';
const webhook = new WebhookClient({ url: process.env.WEBHOOK_URL! });
const opts: WebhookMessageCreateOptions = {
content: 'TS-типизированный payload',
username: 'TS Bot',
};
await webhook.send(opts);
WebhookMessageCreateOptions, EmbedBuilder, AttachmentBuilder — все экспортированы. Автокомплит работает в любом современном TS-редакторе.
Обработка ошибок
Ошибки приходят как DiscordAPIError со структурированными деталями:
import { DiscordAPIError } from 'discord.js';
try {
await webhook.send({ content: 'x'.repeat(2001) });
} catch (err) {
if (err instanceof DiscordAPIError) {
console.error(`Discord отклонил: ${err.code} ${err.message}`);
// err.code — код Discord (50035 = Invalid Form Body)
// err.rawError — полное тело ответа
} else {
throw err;
}
}
Частые коды:
50035— Invalid Form Body (валидация)10015— Unknown Webhook (URL невалидный)10008— Unknown Message (edit/delete с не тем ID)
Cleanup
WebhookClient держит HTTP keep-alive. Если процессы короткоживущие (Lambda, cron) — закрывайте сокеты через destroy():
await webhook.send({ content: 'final' });
webhook.destroy();
Долгоживущие сервисы — оставляйте открытым; reuse коннекта = быстрые burst-отправки.
Попробуйте вживую
Соберите payload визуально в конструкторе Discord Webhook и скопируйте JSON прямо в webhook.send(...). Для других языков — гайды Python и PHP; для raw-HTTP — JavaScript fetch.
Источники
- discord.js docs: WebhookClient
- discord.js docs: EmbedBuilder
- Discord developer docs: Webhook Resource
Попробуйте в нашем инструменте
Открыть конструктор Discord Webhook