Перейти к содержимому

Markdown в Discord Webhook: полный справочник по форматированию

Справочник по markdown в Discord webhook: стили текста, заголовки, код, спойлеры, скрытые ссылки, таймстампы <t:unix:R>, упоминания и allowed_mentions, эмодзи.

7 мин чтения Доступно также на English
На этой странице

Всё, что отправляет вебхук, проходит через тот же рендерер markdown, что и обычное сообщение, набранное в чате. Правила почти те же, но есть пара бонусов, доступных только ботам и вебхукам (например, скрытые ссылки прямо в тексте), и несколько вещей, которые внутри embed ведут себя иначе. Это полный справочник по markdown в Discord webhook: стили текста, заголовки, списки, код, цитаты, спойлеры, ссылки, таймстампы, упоминания, кастомные эмодзи и экранирование всего этого, когда текст приходит от пользователей или из логов.

Все примеры — JSON-тела для POST на https://discord.com/api/webhooks/{id}/{token} с заголовком Content-Type: application/json. Одно правило на всю статью: перенос строки внутри JSON-строки — это \n, а не настоящий Enter, а кавычка — \".

Стили текста

MarkdownРезультат
**жирный**жирный
*курсив* или _курсив_курсив
__подчёркнутый__подчёркнутый текст
~~зачёркнутый~~зачёркнутый
***жирный курсив***жирный курсив
__**подчёркнутый жирный**__подчёркнутый жирный
`код в строке`код в строке
||спойлер||скрыт до клика

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

{
  "content": "**Деплой завершён** — *v2.4.1* выкачена на __production__.\n~~Планировался откат~~ Не понадобился. Сборка: `a1f9c3e`"
}

Заголовки, списки и цитаты

Заголовки — это #, ## и ### в начале строки, после решётки обязателен пробел. Четвёртого уровня нет: #### останется обычным текстом. Списки начинаются с - или * , нумерованные — с 1. ; вложенность даёт отступ в два пробела.

{
  "content": "## Релиз 2.4.1\n### Изменения\n- Быстрее доставка вебхуков\n- Новая команда `/status`\n  - Показывает длину очереди\n### Исправления\n1. Падение на пустом embed\n2. Неверный часовой пояс в футере"
}

Цитаты бывают двух видов. > в начале строки цитирует одну строку. >>> цитирует всё от этого места до конца сообщения, поэтому ставьте его последним:

{
  "content": "Сообщил QA:\n> Кнопка входа не реагирует в Safari\n\nПолный лог:\n>>> 12:01 клик\n12:02 запрос не ушёл\n12:03 ошибка в консоли"
}

Markdown-таблиц в Discord нет. Таблица вида | a | b | придёт как текст с вертикальными чертами. Для колонок используйте поля embed с inline: true, для выравнивания моноширинным шрифтом — блок кода.

Код

Код в строке — одинарные обратные кавычки; блок — три обратные кавычки и, по желанию, имя языка для подсветки:

{
  "content": "Тело запроса:\n```json\n{ \"user\": 42, \"plan\": \"pro\" }\n```\nА `diff` подсвечивает строки красным и зелёным:\n```diff\n- retries: 3\n+ retries: 5\n```"
}

Внутри блока кода ничего больше не парсится: ни жирный, ни упоминания, ни эмодзи. Поэтому это самый безопасный контейнер для вывода логов. Единственное, что в блок кода не положить, — три обратные кавычки подряд; если они есть в самом логе, отправьте его файлом.

Спойлеры, ссылки и превью

||текст|| скрывает текст до клика. Работает вокруг любого строчного содержимого, включая кастомные эмодзи и скрытые ссылки. Для вложений добавьте к имени файла префикс SPOILER_ при загрузке.

Скрытые ссылки [текст](https://example.com) обычные пользователи в чате написать не могут, а вебхук может — прямо в content, а также в описании и полях embed:

{
  "content": "Сборка ||упала|| — смотрите [лог пайплайна](<https://ci.example.com/job/1842>) и [ранбук](<https://wiki.example.com/deploy>).\nБез превью: <https://ci.example.com/job/1842>"
}

Голый URL в content заставляет Discord подтянуть превью под сообщением, и URL внутри скрытой ссылки — тоже. Оберните URL в угловые скобки — <https://…> или [текст](<https://…>), — чтобы превью не было, или передайте "flags": 4 (SUPPRESS_EMBEDS), чтобы отключить все превью в этом сообщении сразу.

Таймстампы: <t:unix:STYLE>

Тег <t:UNIX:STYLE> — единственный способ показать время так, чтобы каждый читатель видел его в своём часовом поясе и локали. UNIX — секунды с 1970-01-01 UTC (не миллисекунды), STYLE — одна буква:

СтильТегКак выглядит (русская локаль)
t<t:1789646400:t>12:00
T<t:1789646400:T>12:00:00
d<t:1789646400:d>17.09.2026
D<t:1789646400:D>17 сентября 2026 г.
f<t:1789646400:f>17 сентября 2026 г., 12:00
F<t:1789646400:F>четверг, 17 сентября 2026 г., 12:00
R<t:1789646400:R>через 3 часа / 2 дня назад

Без стиля получите f. R обновляется вживую — то, что нужно для строк «начнётся через», «истекает через» и «последний раз в сети». Число берётся из шелла командой date +%s или в коде:

const startsAt = Math.floor(Date.now() / 1000) + 3600; // через час

await fetch(process.env.WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    content: `Техработы начнутся <t:${startsAt}:R> (<t:${startsAt}:F>)`,
    allowed_mentions: { parse: [] },
  }),
});
import os
import time
import requests

starts_at = int(time.time()) + 3600
requests.post(os.environ["WEBHOOK_URL"], json={
    "content": f"Техработы начнутся <t:{starts_at}:R> (<t:{starts_at}:F>)"
})

Не путайте с полем timestamp на уровне embed: там строка ISO 8601 ("2026-09-17T12:00:00Z"), она отрисовывается в футере и синтаксис <t:…> не принимает. Сам тег внутри описания и полей embed работает.

Упоминания и allowed_mentions

Упоминание — это ID в угловых скобках: <@userId> — пользователь, <@&roleId> — роль, <#channelId> — канал, плюс буквальные @everyone и @here. Чтобы получить ID, включите режим разработчика в настройках Discord и выберите «Копировать ID» в контекстном меню.

Отрисовка и пинг — разные вещи. Синяя плашка упоминания появляется от одной только разметки, а кого уведомить, решает allowed_mentions. Всегда передавайте его явно:

{
  "content": "<@&123456789012345678> деплой завершён, <@987654321098765432> проверь, пожалуйста. @everyone остаётся без пинга.",
  "allowed_mentions": {
    "parse": [],
    "roles": ["123456789012345678"],
    "users": ["987654321098765432"]
  }
}

"parse": [] отключает автоматический разбор @everyone, @here, всех пользователей и всех ролей; массивы roles и users затем задают точный список тех, кого пинговать. Для текста извне (сообщения коммитов, поля форм) одного {"parse": []} достаточно как безопасного значения по умолчанию. Упоминания внутри embed отрисовываются, но никого не пингуют. Полный набор правил — в руководстве по упоминаниям.

Кастомные эмодзи

Стандартные Unicode-эмодзи вставляются в строку как есть ("content": "Задеплоено 🚀"). Кастомным эмодзи сервера нужна форма с ID: <:name:id> для статичных, <a:name:id> для анимированных. Чтобы её узнать, напишите эмодзи в Discord с обратным слэшем перед ним (\:deploy:) и отправьте — в сообщении окажется сырой тег.

{
  "content": "<:deploy:123456789012345678> Продакшен обновлён <a:party:234567890123456789>"
}

Используйте эмодзи того сервера, которому принадлежит вебхук. Про эмодзи с других серверов сверьтесь с документацией Discord для разработчиков, прежде чем на них полагаться.

Content и embed: в чём разница

Одна и та же разметка парсится по-разному в зависимости от того, где она стоит:

ВозможностьcontentОписание и поля embedЗаголовок, автор, футер embed
Жирный, курсив, код, цитаты, спойлерыДаДаНет (обычный текст)
Заголовки и спискиДаДаНет
Скрытые ссылкиДаДаНет
Таймстампы <t:…>ДаДаНет
УпоминанияОтрисовка и пингОтрисовка без пингаНет
Кастомные эмодзиДаДаНет
Превью для голых URLДаНетНет
Лимит длины20004096 / 1024256 / 256 / 2048

Два практических вывода. Нужна строка-заголовок в поле embed — используйте name поля, оно и так жирное. А если смысл сообщения — пинг, ставьте упоминание в content, а детали — в embed, не наоборот. Цвета, картинки и остальная вёрстка embed разобраны в руководстве по конструктору embed и статье про цвета embed.

Экранирование

Обратный слэш перед символом разметки делает его буквальным: \*не жирный\*, \|\|не спойлер\|\|, \# не заголовок (в начале строки), \> не цитата.

В JSON сам обратный слэш тоже надо экранировать, так что строка превращается в "\\*не жирный\\*". Именно это чаще всего упускают: одиночный \* внутри JSON-строки — невалидный JSON, и Discord ответит 400 Bad Request.

Если текст приходит оттуда, где вы его не контролируете (сообщения коммитов, названия тикетов, ввод пользователей), экранируйте его перед подстановкой:

function escapeMarkdown(text) {
  return text.replace(/([\\*_~`|])/g, '\\$1').replace(/^([>#-])/gm, '\\$1');
}

const commit = 'fix: *очень* важная __штука__ #123';
const content = `Новый коммит: ${escapeMarkdown(commit)}`;
// Новый коммит: fix: \*очень\* важная \_\_штука\_\_ #123
import re

def escape_markdown(text: str) -> str:
    text = re.sub(r"([\\*_~`|])", r"\\\1", text)
    return re.sub(r"^([>#-])", r"\\\1", text, flags=re.M)

Две вещи, которые экранирование не решает. Упоминания слэшем не нейтрализуются — для этого есть allowed_mentions: {"parse": []}. А URL внутри текста всё равно развернутся в превью, если не обернуть их в <…>.

Частые ошибки

  • Переносы строк не появляются. Вы отправили настоящий перенос внутри JSON-строки или забыли \n. Каждый перенос в content и тексте embed должен быть \n.
  • Разметка видна как звёздочки. Пробелы между маркером и текстом (* текст *) либо текст стоит в заголовке, футере или авторе embed, где markdown не рендерится.
  • <@123456789012345678> показывается буквально. ID неверный или вместо ID подставлено имя пользователя. Скопируйте ID через режим разработчика.
  • Упоминание отрисовалось, но никого не пингует. В allowed_mentions нет этого пользователя или роли, либо упоминание стоит внутри embed.
  • Таймстамп показывает бессмысленную дату. Вы передали миллисекунды. Разделите Date.now() на 1000 и округлите вниз.
  • >>> съел остаток сообщения. Так и задумано. Если после цитаты нужен текст, используйте > на каждой строке.
  • 400 Bad Request с JSON-ошибкой. Обычно content длиннее 2000 символов, описание embed длиннее 4096 или неэкранированный слэш либо кавычка в JSON. В теле ответа названо проблемное поле; полный список — в статье про ошибки вебхуков.
  • | таблица | пришла как палочки. В Discord нет синтаксиса таблиц; используйте inline-поля или блок кода.

FAQ

Как сделать таймстамп Discord, который показывает время в часовом поясе читателя?

Используйте <t:UNIX:STYLE>, где UNIX — секунды с начала эпохи, а STYLE — одна из букв t, T, d, D, f, F или R. <t:1789646400:R> отрисуется как «через 3 часа» и обновляется вживую; F даёт полный день недели, дату и время.

Работают ли скрытые ссылки в сообщениях вебхука?

Да. Вебхук может ставить [текст](https://url) прямо в content, и тот же синтаксис работает в описании и полях embed. Превью создают и голые URL, и скрытые ссылки; оберните URL в <>, чтобы его не было.

Почему упоминание отрисовывается, но никого не уведомляет?

Потому что пинг управляется allowed_mentions, а не разметкой. Добавьте ID пользователя или роли в allowed_mentions.users или allowed_mentions.roles и держите упоминание в content: упоминания внутри embed никогда не пингуют.

Можно ли использовать markdown-таблицы в Discord?

Нет. В рендерере Discord нет синтаксиса таблиц. Для сетки используйте поля embed с inline: true, для моноширинных колонок — блок кода.

Что дальше

Самый быстрый способ проверить, как будет выглядеть разметка, — вставить её в конструктор Discord Webhook: предпросмотр рендерит content и embed так же, как клиент, а готовый payload можно выгрузить как JSON или код для discord.js и discord.py. Дальше — руководство по упоминаниям со всеми случаями allowed_mentions, руководство по конструктору embed про вёрстку embed и статья про цвета embed с десятичными значениями для боковой полоски.

Теги: discord webhook markdownформатирование discorddiscord timestamp formatтаймстамп discorddiscord webhookвебхук discordmarkdownallowed_mentions

Похожие статьи

Все статьи

Соберите это в визуальном редакторе

Embed, Components V2, кнопки и опросы с живым предпросмотром Discord. Бесплатно, без регистрации.