A webhook is the simplest way to get a message into a Discord channel: it's just a URL you send an HTTP POST to. No bot, no token, no gateway connection, no intents. Any script in any language that can make a web request can post to it — which makes webhooks perfect for alerts, cron summaries, deploy notifications, and error reports from a Falix app.
| At a glance | |
|---|---|
| You need | a Discord server where you can Manage Webhooks on a channel |
| Plan | any — a webhook needs no server running; but a Falix app is a great thing to post from |
| Time | about ten minutes |
What a webhook is (and isn't)
A webhook points at one channel and goes one direction — you post in, it can't read messages or reply. It has no identity in the member list. That's the trade-off:
| Webhook | Bot | |
|---|---|---|
| Setup | copy a URL | app + token + code |
| Direction | post only | read and respond |
| Reacts to events / commands | no | yes |
| Good for | alerts, logs, notifications | anything interactive |
If all you need is "tell this channel when X happens," a webhook is the right tool. If you need it to listen, you want a bot.
1. Create the webhook
In Discord: open the channel's Edit Channel → Integrations → Webhooks → New Webhook, then Copy Webhook URL. The URL looks like:
https://discord.com/api/webhooks/<id>/<token>
⚠️ Heads up: Treat that URL like a password — anyone who has it can post to your channel as often as they like. Keep it in your
.env, never in your code or a public repo. See Environment variables & secrets.
2. Post to it
The payload is JSON. The fields you'll use most: content (the message text, up to 2000 characters), username and avatar_url to override the webhook's name and picture per message, and embeds for rich cards.
curl:
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"content": "Deploy finished ✅", "username": "CI Bot"}'
Node.js (global fetch, built in on the Node.js application):
await fetch(process.env.WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: 'Deploy finished ✅', username: 'CI Bot' }),
});
Python (requests, installable from the Packages page):
import os, requests
requests.post(os.environ["WEBHOOK_URL"], json={
"content": "Deploy finished ✅",
"username": "CI Bot",
})
What success and failure look like
A good webhook post returns HTTP 204 No Content — an empty body. That's the whole success signal; there's no message ID to read back unless you ask for one with ?wait=true.
A wrong or deleted webhook URL returns 404 with this body:
{"message": "Unknown Webhook", "code": 10015}
That 404 is worth understanding, because it tells you your request was correct — Discord received it, parsed the JSON, and rejected only the (missing) webhook identity. If your method, headers, and body shape were wrong, you'd get a 400 instead. So "Unknown Webhook" means "fix the URL," not "fix the code." A URL with the right ID but a wrong token returns 401 — same lesson.
⚠️ Heads up (Python especially): Discord sits behind Cloudflare, which blocks requests that send no
User-Agentheader — you'll get a403with a Cloudflareerror code: 1010before Discord ever sees it.requests,fetch, andcurlall set a User-Agent for you. If you drop to rawurllib, add one yourself:req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json", "User-Agent": "MyApp/1.0"})
Embeds and formatting
For a titled, colored card instead of plain text, send an embeds array:
body: JSON.stringify({
embeds: [{
title: 'Build #42',
description: 'All checks passed.',
color: 0x57f287,
}],
});
Embeds have the same fields and limits as bot embeds — the embeds deep dive covers them in full.
Behave under rate limits
Webhooks are rate-limited per webhook. For occasional alerts you'll never notice, but don't post in a tight loop — batch several updates into one message, and if you get a 429, wait the Retry-After seconds before trying again. See Discord rate limits.
Great things to post from Falix
- A deploy alert after a Git deploy — pair with a post-deploy schedule.
- Error logs from your app or bot, so you hear about crashes.
- A scheduled summary (daily stats, cron job results).
Troubleshooting
404 Unknown Webhook(code 10015) — the URL is wrong or the webhook was deleted. Copy it again from the channel's Integrations page.401 Unauthorized— right webhook ID, wrong token; re-copy the full URL.403witherror code: 1010— noUser-Agentheader (usually rawurllib). Add one, or userequests.400 Bad Request— malformed body:contentmust be a string,embedsmust be an array, and you must sendContent-Type: application/json.- Posts succeed but land in the wrong channel — a webhook is bound to the channel it was created in. Make one in the channel you actually want.