Python Central HubSign in

Automation and Scripting / Phase 4: Communication Automation

Automating Discord Messages

Send notifications to Discord via webhook URLs.

Discord webhooks

Create a webhook in a Discord channel and store it as:

  • DISCORD_WEBHOOK_URL

Example

Python
import os
import requests
 
 
def discord_send(content: str):
    url = os.environ["DISCORD_WEBHOOK_URL"]
    r = requests.post(url, json={"content": content}, timeout=10)
    r.raise_for_status()
 
 
# discord_send("Automation finished")

How a webhook works

A webhook is a URL that accepts a message. In Discord you open a channel's settings, choose Integrations, and create a webhook. Discord gives you a URL that contains a secret token. Anyone who has that URL can post to the channel, so it is a password. That is why the example reads it from the DISCORD_WEBHOOK_URL environment variable rather than writing it in the code.

Your script does an HTTP POST with a JSON body. {"content": "text"} is the simplest form. Discord replies with status 204 No Content on success. There is no login and no bot to run, which makes webhooks the easiest way to send a notification from a script.

A more useful sender

The basic function fails if Discord is unreachable, and a long message is rejected because content is limited to 2000 characters. This version handles both and adds a username and a formatted error message.

Python
import os
import time
 
import requests
 
LIMIT = 2000
 
 
def discord_send(content: str, username: str = "Automation") -> bool:
    url = os.environ["DISCORD_WEBHOOK_URL"]
    if len(content) > LIMIT:
        content = content[: LIMIT - 3] + "..."
 
    for attempt in range(3):
        r = requests.post(url, json={"content": content, "username": username}, timeout=10)
        if r.status_code == 429:                         # rate limited
            wait = r.json().get("retry_after", 1)
            time.sleep(float(wait))
            continue
        r.raise_for_status()
        return True
    return False
 
 
def notify_failure(job: str, error: Exception) -> None:
    discord_send(f"**{job} failed**\n```\n{type(error).__name__}: {error}\n```")

429 means you are sending too fast, and the response includes retry_after in seconds. Discord formats **bold** and code fences in messages, so the failure message is easy to read in the channel. Use notify_failure inside the except block of a scheduled script so you find out when it breaks.

Common mistakes

  • Committing the webhook URL. If it leaks, delete the webhook in Discord and create a new one.
  • Sending too often. Loops that post on every iteration hit rate limits and annoy people. Send one summary.
  • Sending secrets or personal data to a shared channel.
  • Ignoring errors. raise_for_status() makes a bad URL visible.
  • Using a user account token to post as yourself. That breaks Discord's terms of service. Use webhooks or a proper bot.

Adapting it

Use the same function at the end of your backup, report, or scraper scripts to post "finished" with a count. Add an embeds list to the JSON for richer cards with a title and colour.

Check your understanding

quizCheck yourself
  1. Why should the Discord webhook URL be stored in an environment variable?

    Show answer

    A — The URL contains a secret token, so anyone with it can post to the channel — A webhook URL is effectively a password for the channel. Keep it out of source control, and replace it if it leaks.

  2. Discord responds with HTTP 429. What does it mean and what should the script do?

    Show answer

    B — You are being rate limited, so wait for retry_after seconds and try again — 429 is Too Many Requests. The response says how long to wait, and retrying after that pause is the correct behaviour.

  3. A message is 2500 characters long. What happens if you send it as content?

    Show answer

    B — It is rejected because content is limited to 2000 characters, so truncate or split it first — The API enforces a 2000 character limit on content. Truncate the text or send it as several messages.

Was this page helpful?

Let us know how we did