Post to a Discord webhook from any code

A webhook is a URL that drops a message into one channel — no bot, no token, no gateway. Post to it from Node, Python, or curl. Includes the exact success and failure responses Discord returns, verified against the real API.

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-Agent header — you'll get a 403 with a Cloudflare error code: 1010 before Discord ever sees it. requests, fetch, and curl all set a User-Agent for you. If you drop to raw urllib, 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.
  • 403 with error code: 1010 — no User-Agent header (usually raw urllib). Add one, or use requests.
  • 400 Bad Request — malformed body: content must be a string, embeds must be an array, and you must send Content-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.

Next steps

Was this guide helpful?