Discord webhook je URL, která posílá zprávy do jednoho kanálu. Vytvoříš ho v nastavení kanálu, pošleš na něj HTTP POST s malým tělem v JSONu a zpráva se objeví pod jménem a avatarem, které sis zvolil. Žádný účet bota, žádný token k registraci, žádná knihovna k instalaci a žádný proces, který by musel běžet. Díky tomu je webhook správný nástroj pro jednosměrné zprávy ze softwaru - nasazení doběhlo, odeslal se formulář, selhal backup, vyšlo nové video - a špatný nástroj pro cokoli, co musí poslouchat nebo odpovídat.
Všechno níže funguje z jakéhokoli jazyka a z jakéhokoli stroje, který umí poslat HTTPS požadavek. Potíže přicházejí až později: URL je jediné přihlašovací údaje a nikdy nevyprší, pár parametrů v dotazu (wait, thread_id) mění chování úplně a webhook, který narazí na rate limit, ti přesně řekne, jak dlouho máš čekat - když si přečteš odpověď. Pokud chceš živou stavovou zprávu pro herní server, najdeš ji od začátku do konce v článku Discord webhooky pro upozornění herního serveru; tenhle článek je obecná příručka, na které stojí.
Co je webhook a URL, která je celá jeho podstata#
Webhook patří ke kanálu. Má číselné id a dlouhý náhodný token a URL je prostě obojí spojené dohromady:
https://discord.com/api/webhooks/1290000000000000000/Xy7k...the-token...Qp2Kdokoli, kdo na tuhle URL pošle požadavek se správným tělem, může do kanálu psát. Žádný druhý faktor, žádné přihlášení, žádný rozsah oprávnění mimo ten jeden kanál a žádná expirace. URL je heslo a platí, dokud někdo webhook nesmaže. Tahle jediná skutečnost určuje většinu bezpečnostních rad na konci článku.
Zpráva odeslaná přes webhook ukazuje jméno a avatar webhooku (nebo ty, které přepíšeš u konkrétní zprávy) a vedle jména malou značku, že jde o automatickou zprávu. Webhook není členem serveru, není v seznamu členů a nemá role. Nelze ho zmínit, nelze mu poslat přímou zprávu a nevidí nic, co se v kanálu děje - ani zprávy, které sám poslal, kromě speciálních endpointů pro jejich načtení, úpravu nebo smazání.
Webhooky se objevují i na dvou místech, kde bys je nečekal:
- Sledování oznamovacího kanálu. Když server sleduje oznamovací kanál jiného serveru, Discord vytvoří v cílovém kanálu webhook, který doručuje sdílené příspěvky. V seznamu integrací je vidět jako každý jiný.
- Integrace, které posílají zprávy za tebe. Mnoho služeb (CI systémy, monitorovací nástroje, samotný GitHub) chce po tobě URL webhooku, a ne instalaci bota, právě proto, že webhook nepotřebuje žádná oprávnění mimo jeden kanál.
Vytvoření webhooku v nastavení kanálu#
Potřebuješ oprávnění Manage Webhooks v daném kanálu, které mají majitelé serveru a administrátoři ve výchozím stavu.
- Najeď na kanál a klikni na ozubené kolečko (Edit Channel), nebo na kanál klikni pravým tlačítkem a vyber Edit Channel.
- Otevři Integrations a pak Webhooks.
- Klikni na New Webhook. Discord ho hned vytvoří s vygenerovaným jménem.
- Klikni na něj, aby se rozbalil, nastav jméno, případně nahraj avatar a zkontroluj, že rozbalovací seznam kanálů ukazuje ten, který chceš.
- Klikni na Copy Webhook URL a pokud jsi něco měnil, pak na Save Changes.
Server Settings, Integrations, Webhooks ukazuje totéž pro celý server: každý webhook v každém kanálu a kdo ho vytvořil. Na této stránce se dělá audit a úklid a audit log serveru zaznamenává vytvoření, změny i smazání webhooků.
V době psaní platí několik praktických limitů: kanál může mít nejvýše 15 webhooků, jméno webhooku musí mít 1 až 80 znaků a jména obsahující slova discord nebo clyde se odmítají. Discord je občas upravuje, takže pokud vytvoření selže s chybou o limitech, zkontroluj vývojářskou dokumentaci.
Pojmenuj webhook podle zdroje - CI, Backups, Shop orders - a nenechávej "Captain Hook" nebo jiné vygenerované jméno. Jméno je výchozí autor každé zprávy a štítek, podle kterého budeš hledat v seznamu integrací, když se něco chová divně.
První zpráva pomocí curl#
Nejmenší platný požadavek je POST s tělem v JSONu, které obsahuje content:
$ curl -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d '{"content": "Deploy finished on main."}'Při úspěchu Discord odpoví 204 No Content s prázdným tělem. To není chyba a není to náhoda: ve výchozím stavu API nečeká, až se zpráva uloží, a odpoví hned. Přidej ?wait=true a dostaneš 200 OK s celým objektem zprávy, včetně jejího id, které potřebuješ, abys ji později upravil nebo smazal.
Pole, která použiješ téměř u každé zprávy:
| Pole | Co dělá |
|---|---|
content | Text zprávy, nejvýše 2000 znaků, Discord markdown |
username | Přepíše jméno webhooku jen pro tuto zprávu |
avatar_url | Přepíše avatar, přímý odkaz na obrázek |
embeds | Až 10 objektů embed (karty s nadpisem a poli) |
allowed_mentions | Určuje, které zmínky skutečně upozorní |
flags | 4096 pošle zprávu tiše, bez oznámení |
tts | Přečte zprávu nahlas lidem se zapnutým TTS |
Musí být přítomno aspoň jedno z content, embeds, souboru nebo ankety, jinak požadavek selže s 400. Úplný příklad s přepsanou identitou a vypnutými zmínkami:
$ curl -X POST "$WEBHOOK_URL?wait=true" \ -H "Content-Type: application/json" \ -d '{ "username": "Shop", "avatar_url": "https://example.com/shop-icon.png", "content": "New order #1042 - 2 items, paid by card", "allowed_mentions": {"parse": []} }'avatar_url musí ukazovat přímo na soubor s obrázkem, který Discord umí stáhnout - stránka, na které je obrázek vložený, nestačí, a odkaz vyžadující přihlášení se tiše změní na výchozí avatar. username a avatar_url platí jen pro tu jednu zprávu; uložené jméno a avatar webhooku se nemění.
Obsah přijímá běžný Discord markdown: **bold**, inline kód, bloky kódu, odkazy s textem jako [changelog](https://example.com), zmínky uživatelů jako <@USER_ID>, zmínky rolí jako <@&ROLE_ID> a časová razítka jako <t:1760000000:R>, která se každému čtenáři zobrazí jako "před 3 hodinami" v jeho vlastním časovém pásmu. O tom, jestli zmínka skutečně upozorní, rozhoduje allowed_mentions. Prázdné pole parse, jako výše, znamená, že neupozorní nikoho, což je bezpečné výchozí nastavení pro všechno, co přeposílá text, který jsi nenapsal sám. Chceš-li upozornit jednu konkrétní roli a nic jiného, uveď ji výslovně:
{ "content": "<@&123456789012345678> the nightly build failed", "allowed_mentions": { "parse": [], "roles": ["123456789012345678"] }}Když je něco špatně, Discord odpoví 400 s tělem v JSONu, které pojmenuje odmítnuté pole. Přečti si ho dřív, než začneš cokoli jiného měnit - skoro vždy je konkrétnější, než čekáš.
Odesílání z Pythonu a JavaScriptu#
Na webhook není potřeba žádná knihovna pro Discord. Stačí jakýkoli HTTP klient a kód je tak krátký, že závislost je volitelná.
Python s requests:
import osimport requestsurl = os.environ["WEBHOOK_URL"]payload = { "username": "Backups", "content": "Nightly backup finished: 412 MB in 38 s", "allowed_mentions": {"parse": []},}r = requests.post(url, json=payload, params={"wait": "true"}, timeout=10)r.raise_for_status()print("message id:", r.json()["id"])Pokud projekt už používá discord.py 2.x, má synchronní klient pro webhooky, který nepotřebuje bota ani event loop:
from discord import SyncWebhookhook = SyncWebhook.from_url(os.environ["WEBHOOK_URL"])msg = hook.send("Deploy finished", username="CI", wait=True)print(msg.id)JavaScript na Node 18 a novější má fetch zabudovaný, takže základní verze nepotřebuje žádné balíčky:
const res = await fetch(`${process.env.WEBHOOK_URL}?wait=true`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "CI", content: "Build 512 passed", allowed_mentions: { parse: [] }, }),});if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);const message = await res.json();console.log(message.id);discord.js má ekvivalent ve WebhookClient, který se vytváří pomocí new WebhookClient({ url }) a používá se s send(). Rate limity řeší za tebe, což se hodí, pokud skript posílá hodně zpráv.
V každém jazyce drž URL v proměnné prostředí a nikdy ve zdrojovém kódu. Zbytek kódu jsou pořád tři kroky: sestavit slovník, poslat ho jako JSON, zkontrolovat stavový kód. Kam patří hodnota na serveru, popisuje článek Proměnné prostředí a tajné údaje.
Embedy, soubory a limity, které vracejí 400#
Prostý content je správně pro jednořádková oznámení. Pro cokoli se strukturou - objednávku s několika poli, build s commitem a autorem, report s barvou, která znamená dobré nebo špatné - použij embed:
{ "username": "CI", "embeds": [{ "title": "Build 512 passed", "url": "https://example.com/builds/512", "description": "main - fix login redirect", "color": 3066993, "fields": [ { "name": "Duration", "value": "2m 14s", "inline": true }, { "name": "Tests", "value": "418 passed", "inline": true } ], "footer": { "text": "runner-02" }, "timestamp": "2026-10-15T09:30:00.000Z" }]}Na dvou detailech se zasekne téměř každý. color je desetinné celé číslo, takže hexadecimální barva #2ECC71 se posílá jako 3066993; řetězec tam znamená 400. A timestamp musí být ve formátu ISO 8601, který Discord pak zobrazí v místním čase každého čtenáře.
Limity se vynucují, ne zkracují: 2000 znaků v content, 10 embedů na zprávu, 256 znaků v titulku embedu, 4096 v popisu, 25 polí s názvy do 256 znaků a hodnotami do 1024 znaků a 6000 znaků ve všech embedech jedné zprávy dohromady. Článek o stavových zprávách serveru má úplnou tabulku limitů i to, co se stane, když je stack trace překročí. Zkracuj na své straně ještě před odesláním.
Soubory se nahrávají jako multipart/form-data, ne jako JSON. Tělo v JSONu se přesune do formulářového pole payload_json a každý soubor je pole s názvem files[0], files[1] a tak dále:
$ curl -X POST "$WEBHOOK_URL" \ -F 'payload_json={"content": "Error log from the failed run"}' \ -F 'files[0]=@build.log'Limit velikosti nahrávaného souboru závisí na úrovni boostu serveru a Discord ho změnil už vícekrát; na serveru bez boostu je teď 10 MB na zprávu. Větší logy patří do objektového úložiště nebo logovacího systému a do zprávy patří odkaz.
Dva kompatibilní endpointy ušetří psaní adaptéru. Připojením /slack k URL webhooku se přijme payload ve formátu Slacku a /github přijme formát webhooků GitHubu, takže můžeš vložit https://discord.com/api/webhooks/ID/TOKEN/github do nastavení webhooku v repozitáři (typ obsahu application/json) a dostávat oznámení o commitech a pull requestech bez jediného řádku kódu.
wait=true, vlákna a fóra#
Řetězec dotazu v URL pro odeslání mění chování a většinu práce dělají dva parametry.
| Parametr | Účinek |
|---|---|
wait=true | Odpoví 200 s uloženou zprávou místo 204 |
thread_id=ID | Pošle zprávu do existujícího vlákna v kanálu webhooku |
Použij wait=true vždy, když se zprávy budeš ještě dotýkat, nebo když chceš mít jistotu, že se skutečně uložila, než skript půjde dál. Bez něj ti 204 říká jen to, že požadavek byl přijat, nic víc.
thread_id umožňuje jednomu webhooku krmit mnoho vláken. Vlákno musí patřit ke kanálu webhooku - webhook v #builds může psát do jakéhokoli vlákna pod #builds, ne do vlákna jinde. Běžný postup je jedno vlákno na vydání nebo incident, kam se posílají všechny související zprávy, takže nadřazený kanál zůstane přehledný.
Fóra a media kanály nemají běžnou oblast pro zprávy, jen příspěvky, takže webhook tam musí říct, který příspěvek myslí. Buď předej thread_id, abys odpověděl do existujícího příspěvku, nebo pošli v těle thread_name, čímž vytvoříš nový příspěvek se svou zprávou jako první zprávou:
$ curl -X POST "$WEBHOOK_URL?wait=true" \ -H "Content-Type: application/json" \ -d '{"thread_name": "Incident 2026-10-15", "content": "API returning 502 since 09:12"}'Odpověď na ten požadavek obsahuje zprávu a její channel_id je id nového vlákna - schovej si ho, pokud budeš posílat další zprávy. Požadavek na webhook fóra bez thread_id i thread_name selže s 400. Pokud fórum vyžaduje štítky, applied_tags přijímá seznam id štítků.
Úpravy, mazání a správa zpráv#
Webhook může měnit jen zprávy, které sám poslal, a adresuje je podle id pod svou vlastní URL:
# Read a message the webhook sent$ curl "$WEBHOOK_URL/messages/$MESSAGE_ID"# Edit it - only the fields you send are changed$ curl -X PATCH "$WEBHOOK_URL/messages/$MESSAGE_ID" \ -H "Content-Type: application/json" \ -d '{"content": "Deploy finished on main (rolled back 09:40)"}'# Delete it$ curl -X DELETE "$WEBHOOK_URL/messages/$MESSAGE_ID"Pokud zpráva leží ve vlákně, přidej ke každé z těch URL i ?thread_id=THREAD_ID, jinak ji Discord hledá v nadřazeném kanálu a vrátí 404. Úprava nechá původní jméno autora a avatar beze změny; username a avatar_url se při úpravě nepřijímají. Chceš-li smazat embedy, pošli "embeds": []; chceš-li smazat text, pošli "content": "" (ve zprávě pak ale musí zůstat něco jiného).
Úpravy nikoho neupozorňují. Proto je úprava na místě správný tvar pro všechno, co se opakovaně mění - řádek s průběhem, délku fronty, živou stavovou kartu - a nový příspěvek je správný tvar pro události, kterých si mají lidé všimnout. Článek o stavu serveru kolem toho staví malý stavový automat.
Samotný webhook se spravuje přes stejnou URL:
GET $WEBHOOK_URLvrátí objekt webhooku - jeho jméno, id kanálu, id serveru a hash avatara. Pokud se načítá jen s tokenem, vynechá uživatele, který ho vytvořil. Je to levný způsob, jak ověřit, že uložená URL stále funguje.PATCH $WEBHOOK_URLsnamea/neboavatarzmění uloženou identitu.avatarje data URI (data:image/png;base64,...), ne odkaz. Přesunutí webhooku do jiného kanálu vyžaduje token bota nebo uživatele, ne token webhooku.DELETE $WEBHOOK_URLwebhook smaže. Každý další požadavek na URL pak vrací404, nadobro.
To poslední je důležité: kdokoli, kdo drží URL, může webhook smazat, což se hodí, když najdeš uniklý webhook v cizím pastu, a vadí, když to omylem udělá nedbalý skript.
Rate limity a odpověď 429#
Discord omezuje každou routu, webhooky nevyjímaje. Praktický strop pro jeden webhook je zhruba pět požadavků za dvě sekundy a kanál přijme zhruba třicet zpráv za minutu bez ohledu na to, kolik webhooků do něj píše. Tato čísla nejsou zveřejněná jako pevná a mění se, takže nezadávej napevno pauzu a nepovažuj to za vyřešené. Čti odpověď.
Když limit překročíš, odpověď je 429 Too Many Requests s hlavičkami a tělem:
{ "message": "You are being rate limited.", "retry_after": 0.53, "global": false }retry_after je v sekundách a může být desetinné. Počkej tak dlouho a pak stejný požadavek zopakuj jednou. Hlavičky X-RateLimit-Remaining a X-RateLimit-Reset-After dovolí pečlivému klientovi zpomalit dřív, než do zdi vůbec narazí. Minimální opakování v Pythonu:
import time, requestsdef send(url, payload, attempts=5): for _ in range(attempts): r = requests.post(url, json=payload, timeout=10) if r.status_code != 429: r.raise_for_status() return r time.sleep(float(r.json().get("retry_after", 1))) raise RuntimeError("still rate limited")Webhooky nedostává do potíží občasné 429, ale reakce na ně. Skript, který okamžitě opakuje ve smyčce, promění půlsekundové čekání v záplavu a opakované neplatné požadavky (odpovědi 401, 403 a 429) mohou odesílající IP adrese vynést dočasnou blokaci celého API Discordu. Slučuj, kde to jde - deset řádků logu spojených novými řádky je jeden požadavek - a ustupuj i při chybách 5xx, které Discord vrací při zátěži.
Uniklá URL webhooku: co dělat#
URL webhooků unikají všedními způsoby: commitnuté v konfiguračním souboru, vložené do chatu podpory, viditelné na screenshotu, vypsané v logu CI, vložené do klientského JavaScriptu webu. To poslední je nejčastější a nejsnáze se mu dá předejít - každá URL, která se dostane do prohlížeče, je veřejná, a existují scrapery, které hledají přesně tenhle vzor a používají ho k posílání spamu nebo phishingových odkazů pod hlavičkou tvého serveru.
Když jedna unikne, úklid je krátký a dělá se v tomto pořadí:
- Smaž webhook. Nastavení kanálu, Integrations, Webhooks, vyber ho, Delete Webhook. Nebo
curl -X DELETE "$WEBHOOK_URL"odkudkoli. Stará URL přestane fungovat okamžitě. Možnost "resetovat token" neexistuje; smazání a znovuvytvoření je rotace. - Vytvoř nový ve stejném kanálu a novou URL vlož do proměnné prostředí nebo úložiště tajných údajů, ze kterého odesílatel čte. Odesílatele restartuj, aby ji načetl.
- Ukliď kanál. Smaž všechno, co útočník poslal. Už odeslané zprávy po zániku webhooku zůstávají, a když žádný webhook není, mažeš je ručně jako moderátor.
- Odstraň zdroj. Přepiš soubor, zruš paste, rotuj log CI. Pokud to bylo v repozitáři Git, smazání v novém commitu nestačí - zůstává v historii a uniklá URL je každopádně mrtvá, takže se soustřeď na to, aby se ta nová nikdy nedostala stejnou cestou.
- Zkontroluj zbytek. Server Settings, Integrations, Webhooks vypíše každý webhook na serveru. Smaž ty, za které nikdo nemůže ručit.
Zacházej s URL jako s heslem k databázi: proměnná prostředí na serveru, který odesílá, začerněná na screenshotech a v lozích, a jedna na každého odesílatele, aby jeden únik znamenal jedno smazání.
Webhook, nebo bot: výběr správného nástroje#
Rozhodnutí je o směru. Webhook zapisuje do kanálu. Bot se připojí k bráně Discordu, přijímá události a může na ně reagovat.
| Chceš | Použij |
|---|---|
| Posílat oznámení ze skriptu, CI nebo webu | Webhook |
| Později je upravovat nebo mazat | Webhook |
| Psát pod několika jmény a avatary | Webhook |
| Reagovat na slash příkazy nebo tlačítka | Bot |
| Číst chat, reakce nebo příchody členů | Bot |
| Přidělovat role, moderovat, posílat DM | Bot |
| Psát na mnoha serverech, které nevlastníš | Bot, pozvaný na každý |
Webhook nepotřebuje, aby mezi zprávami něco běželo, takže jeho držení nestojí nic. Bot je dlouho běžící proces s tokenem, intenty a spojením s bránou, které musí zůstat živé; když se zastaví, je vidět jako offline a jeho příkazy selhávají. To je samostatný hostingový problém, který řeší článek hosting Discord bota 24/7 pro discord.js a hosting bota v discord.py pro Python.
Za povšimnutí stojí dva hybridy. Bot může webhooky vytvářet sám a používat je k psaní pod jinými identitami - tak chatové mosty ukazují hráče ze hry s jejich vlastními jmény a avatary. A webhook vytvořený aplikací (a ne člověkem v nastavení kanálu) může posílat interaktivní komponenty jako tlačítka, protože interakce se vracejí k té aplikaci. Webhook vytvořený v nastavení kanálu nemůže nic přijímat, takže tlačítka na něm by neměla komu hlásit.
Existuje také samostatná funkce, kterou Discord nazývá webhook events: aplikace může ve vývojářském portálu zaregistrovat HTTPS endpoint a přijímat určité události, například autorizaci aplikace, jako příchozí požadavky. To je opačný směr - Discord volá tebe - a je to pro vývojáře aplikací, ne způsob, jak číst kanál.
Kde odesílatel běží, je poslední rozhodnutí. Webhook potřebuje jen něco, co umí poslat požadavek, když nastane událost: úlohu CI, cron úlohu, backend, který už existuje. Pokud neexistuje, stačí malý aplikační server, který běží pořád. Tarify aplikací RE:NODE pro Node.js a Python začínají na 4 USD měsíčně, URL webhooku se drží jako proměnná prostředí na záložce Startup a nasazuje se rovnou z GitHubu.
FAQ#
Musím programovat, abych mohl používat Discord webhook?
Ne. Mnoho služeb vezme URL webhooku a odesílání udělá za tebe - vložíš ji a ony posílají. K odeslání vlastních zpráv stačí jediný příkaz curl a existují i webové nástroje, které za tebe sestaví JSON. Kód je potřeba, až když tvůj vlastní program rozhoduje, co a kdy poslat.
Proč můj webhook vrací 204 a žádné id zprávy?
Protože 204 No Content je výchozí úspěšná odpověď. Přidej k URL ?wait=true a Discord vrátí 200 s celou zprávou, včetně id, které potřebuješ k úpravě nebo smazání.
Může webhook psát do více kanálů?
Ne. Každý webhook patří k jednomu kanálu a pomocí thread_id dosáhne jen na vlákna uvnitř toho kanálu. Pro více kanálů vytvoř webhook v každém, což je lepší i kvůli únikům a šumu.
Může webhook upozornit roli?
Ano. Vlož <@&ROLE_ID> do content a přidej id do allowed_mentions.roles, přičemž parse nech prázdné, aby neupozornilo nic jiného. Id role získáš pravým kliknutím na roli se zapnutým Developer Mode v pokročilém nastavení Discordu, stejně tak zkopíruješ id uživatelů, kanálů a zpráv.
Vyprší URL webhooku?
Ne. Webhook funguje, dokud není smazán, dokud není smazán jeho kanál nebo dokud ho server neodstraní. URL, která loni fungovala a dnes vrací 404, byla někým smazána - zkontroluj Server Settings, Integrations, Webhooks.
Proč webhook odmítá moje uživatelské jméno?
Jména musí mít 1 až 80 znaků a nesmí obsahovat discord ani clyde. Tělo odpovědi 400 říká, které pravidlo porušilo; jméno zkrať nebo to slovo vynech.


