DevLab / utilities / discord-webhook-alert-sender
UTILITIES PYTHON BEGINNER

Discord Webhook Alert Sender

In modern DevOps and system administration, getting notified about critical events in real-time is essential. While email alerts are common, they often get

Python3.10+
OSLinux / macOS / Windows
Lines~252
Packages1 deps
Install Dependencies
$ pip install requests
Usage
$ python3 discord_alert.py --webhook $DISCORD_URL --level critical
#utilities#python#beginner#requests

Discord Webhook Alert Sender

Problem Statement

In modern DevOps and system administration, getting notified about critical events in real-time is essential. While email alerts are common, they often get buried in inboxes or delayed by spam filters. Discord webhooks provide a direct, instant notification channel to your team's Discord server, making them ideal for alerting on system failures, deployment completions, or security incidents.

This script solves the problem of sending formatted alerts to Discord without requiring a full Discord bot setup. By leveraging Discord's webhook API, you can send rich, color-coded alerts with proper formatting, timestamps, and severity levels directly from any script, cron job, or CI/CD pipeline.

How It Works

The script follows a simple, clean architecture: it parses command-line arguments to determine the alert parameters, constructs a rich Discord embed message with appropriate colors and formatting based on severity level, and sends it via HTTP POST to the Discord webhook endpoint using the requests library.

Key design decisions include using environment variables for the webhook URL (keeping credentials out of the command line), implementing a color-coding system for different severity levels (critical=red, warning=yellow, info=blue), and providing flexible message customization through command-line arguments. The script uses Python's argparse for robust CLI parsing and includes comprehensive error handling to catch network failures, invalid webhook URLs, and Discord API errors.

Full Script

#!/usr/bin/env python3
"""
Discord Webhook Alert Sender

A production-ready utility for sending formatted alerts to Discord channels
via webhooks. Supports multiple severity levels with color-coded embeds,
custom messages, and optional metadata fields.

Usage:
    python3 discord_alert.py --webhook $DISCORD_URL --level critical --message "Server down!"
    python3 discord_alert.py --level warning --title "High CPU Usage" --message "CPU at 95%"
    python3 discord_alert.py --info --message "Deployment completed successfully"
"""

import argparse
import json
import os
import sys
from datetime import datetime, timezone
from typing import Dict, Optional

import requests


# Configuration section - environment variables with sensible defaults
WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL", "")
DEFAULT_USERNAME = os.environ.get("DISCORD_BOT_NAME", "Alert Bot")
DEFAULT_AVATAR_URL = os.environ.get("DISCORD_AVATAR_URL", "")

# Severity configuration: color mapping and default titles
SEVERITY_CONFIG = {
    "critical": {
        "color": 0xFF0000,  # Red
        "default_title": "๐Ÿšจ CRITICAL ALERT",
        "emoji": "๐Ÿ”ด",
    },
    "warning": {
        "color": 0xFFA500,  # Orange
        "default_title": "โš ๏ธ WARNING",
        "emoji": "๐ŸŸก",
    },
    "info": {
        "color": 0x0000FF,  # Blue
        "default_title": "โ„น๏ธ INFO",
        "emoji": "๐Ÿ”ต",
    },
    "success": {
        "color": 0x00FF00,  # Green
        "default_title": "โœ… SUCCESS",
        "emoji": "๐ŸŸข",
    },
}


class DiscordAlertError(Exception):
    """Custom exception for Discord alert sending failures."""
    pass


def parse_arguments() -> argparse.Namespace:
    """Parse command-line arguments for the alert sender."""
    parser = argparse.ArgumentParser(
        description="Send formatted alerts to Discord via webhooks",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="Examples:\n"
               "  python3 discord_alert.py --level critical --message 'Server down!'\n"
               "  python3 discord_alert.py --warning --title 'High CPU' --message 'CPU at 95%'\n"
               "  python3 discord_alert.py --info --message 'Deployment complete'"
    )
    
    # Mutually exclusive group for severity flags
    severity_group = parser.add_mutually_exclusive_group()
    severity_group.add_argument(
        "--level", 
        choices=["critical", "warning", "info", "success"],
        help="Severity level of the alert"
    )
    severity_group.add_argument(
        "--critical", 
        action="store_const", 
        const="critical", 
        dest="level",
        help="Shorthand for --level critical"
    )
    severity_group.add_argument(
        "--warning", 
        action="store_const", 
        const="warning", 
        dest="level",
        help="Shorthand for --level warning"
    )
    severity_group.add_argument(
        "--info", 
        action="store_const", 
        const="info", 
        dest="level",
        help="Shorthand for --level info"
    )
    severity_group.add_argument(
        "--success", 
        action="store_const", 
        const="success", 
        dest="level",
        help="Shorthand for --level success"
    )
    
    parser.add_argument(
        "--message", 
        type=str, 
        required=True,
        help="Alert message content (required)"
    )
    parser.add_argument(
        "--title", 
        type=str,
        help="Custom title for the alert (overrides default)"
    )
    parser.add_argument(
        "--webhook", 
        type=str,
        help="Discord webhook URL (overrides environment variable)"
    )
    parser.add_argument(
        "--username", 
        type=str,
        help="Bot username shown in Discord"
    )
    parser.add_argument(
        "--avatar", 
        type=str,
        help="Bot avatar URL shown in Discord"
    )
    
    return parser.parse_args()


def build_embed(
    level: str,
    message: str,
    title: Optional[str] = None,
) -> Dict:
    """
    Build a Discord embed message with proper formatting.
    
    Args:
        level: Severity level (critical, warning, info, success)
        message: Alert message content
        title: Custom title (optional)
    
    Returns:
        Dictionary containing the embed structure for Discord API
    """
    config = SEVERITY_CONFIG.get(level, SEVERITY_CONFIG["info"])
    
    # Use custom title if provided, otherwise use default
    embed_title = title if title else config["default_title"]
    
    # Create timestamp in ISO 8601 format
    timestamp = datetime.now(timezone.utc).isoformat()
    
    embed = {
        "title": embed_title,
        "description": message,
        "color": config["color"],
        "timestamp": timestamp,
        "footer": {
            "text": f"Alert Level: {level.upper()}",
            "icon_url": DEFAULT_AVATAR_URL if DEFAULT_AVATAR_URL else None,
        },
        "fields": [
            {
                "name": "Severity",
                "value": f"{config['emoji']} {level.capitalize()}",
                "inline": True
            },
            {
                "name": "Timestamp",
                "value": f"<t:{int(datetime.now(timezone.utc).timestamp())}:F>",
                "inline": True
            }
        ]
    }
    
    return embed


def send_discord_alert(
    webhook_url: str,
    message: str,
    level: str = "info",
    title: Optional[str] = None,
    username: Optional[str] = None,
    avatar_url: Optional[str] = None,
) -> bool:
    """
    Send an alert to Discord via webhook.
    
    Args:
        webhook_url: Discord webhook URL
        message: Alert message text
        level: Severity level
        title: Custom title (optional)
        username: Bot display name (optional)
        avatar_url: Bot avatar URL (optional)
    
    Returns:
        True if successful, raises DiscordAlertError on failure
    
    Raises:
        DiscordAlertError: If webhook URL is invalid or request fails
    """
    if not webhook_url:
        raise DiscordAlertError(
            "No webhook URL provided. Set DISCORD_WEBHOOK_URL environment "
            "variable or use --webhook argument."
        )
    
    if not webhook_url.startswith("https://discord.com/api/webhooks/"):
        raise DiscordAlertError(
            "Invalid Discord webhook URL. Must start with "
            "'https://discord.com/api/webhooks/'"
        )
    
    # Build the embed
    embed = build_embed(level, message, title)
    
    # Construct the full payload
    payload = {
        "username": username or DEFAULT_USERNAME,
        "embeds": [embed],
    }
    
    # Add avatar if provided
    if avatar_url or DEFAULT_AVATAR_URL:
        payload["avatar_url"] = avatar_url or DEFAULT_AVATAR_URL
    
    # Send the request with timeout
    try:
        response = requests.post(
            webhook_url,
            data=json.dumps(payload),
            headers={"Content-Type": "application/json"},
            timeout=10
        )
        response.raise_for_status()
        return True
        
    except requests.exceptions.Timeout:
        raise DiscordAlertError("Request timed out after 10 seconds")
        
    except requests.exceptions.ConnectionError as e:
        raise DiscordAlertError(f"Connection error: {str(e)}")
        
    except requests.exceptions.HTTPError as e:
        # Discord returns 400 for invalid payloads, 404 for invalid webhooks
        if response.status_code == 404:
            raise DiscordAlertError("Webhook not found. Check the URL.")
        elif response.status_code == 400:
            raise DiscordAlertError(f"Invalid request payload: {response.text}")
        else:
            raise DiscordAlertError(f"HTTP error {response.status_code}: {response.text}")
            
    except requests.exceptions.RequestException as e:
        raise DiscordAlertError(f"Request failed: {str(e)}")


def main() -> None:
    """Main entry point for the script."""
    args = parse_arguments()
    
    # Determine webhook URL (CLI arg takes precedence over env var)
    webhook_url = args.webhook or WEBHOOK_URL
    
    # Default level to info if not specified
    level = args.level or "info"
    
    try:
        # Send the alert
        success = send_discord_alert(
            webhook_url=webhook_url,
            message=args.message,
            level=level,
            title=args.title,
            username=args.username,
            avatar_url=args.avatar,
        )
        
        if success:
            print(f"โœ… Alert sent successfully (level: {level})")
            
    except DiscordAlertError as e:
        print(f"โŒ Failed to send alert: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

Configuration

The script uses the following environment variables (all optional if you provide CLI arguments):

Variable Description Default
DISCORD_WEBHOOK_URL Your Discord webhook URL (found in Discord server settings โ†’ Integrations โ†’ Webhooks) Empty string
DISCORD_BOT_NAME Display name for the bot in Discord "Alert Bot"
DISCORD_AVATAR_URL URL to an image for the bot's avatar Empty string

To create a Discord webhook:

  1. Open your Discord server settings
  2. Navigate to Integrations โ†’ Webhooks
  3. Click New Webhook
  4. Name it and select the channel
  5. Copy the webhook URL

Usage Examples

Example 1: Critical Alert

python3 discord_alert.py --webhook $DISCORD_URL --level critical --message "Production database is down! Immediate attention required."

Output:

โœ… Alert sent successfully (level: critical)

Discord shows: Red embed titled "๐Ÿšจ CRITICAL ALERT" with the message, severity field showing "๐Ÿ”ด Critical", and timestamp.

Example 2: Warning with Custom Title

python3 discord_alert.py --warning --title "High Memory Usage" --message "Server memory usage exceeded 90% on host web-01"

Output:

โœ… Alert sent successfully (level: warning)

Discord shows: Orange embed titled "High Memory Usage" with the message and warning emoji.

Example 3: Success Alert for CI/CD

python3 discord_alert.py --success --message "Deployment v2.3.1 completed successfully in 45 seconds" --username "CI Bot"

Output:

โœ… Alert sent successfully (level: success)

Discord shows: Green embed titled "โœ… SUCCESS" from "CI Bot" username.

Error Handling

The script handles several error scenarios gracefully:

  1. Missing Webhook URL: If neither the DISCORD_WEBHOOK_URL environment variable nor the --webhook argument is provided, the script exits with an error message.

  2. Invalid Webhook URL: URLs that don't start with https://discord.com/api/webhooks/ are rejected before making any network requests.

  3. Network Timeouts: Requests have a 10-second timeout. If Discord doesn't respond in time, a DiscordAlertError is raised.

  4. Connection Errors: Network connectivity issues are caught and reported with the specific error message.

  5. Discord API Errors:

    • 404 status code indicates the webhook was deleted or the URL is incorrect
    • 400 status code indicates malformed payload (shouldn't happen with this script, but handled)
    • Other HTTP errors are reported with the response body for debugging
  6. Exit Codes: The script uses sys.exit(1) on failure, making it suitable for use in cron jobs or CI/CD pipelines where exit codes matter.

Extending Ideas

  • Message Queue Support: Add integration with Redis or RabbitMQ to queue alerts during network outages and retry sending them later.

  • Multiple Webhook Support: Extend the script to send alerts to multiple Discord channels simultaneously by accepting a comma-separated list of webhook URLs.

  • Alert Deduplication: Implement rate limiting or deduplication logic to prevent alert fatigue from repeated identical alerts.

  • Dynamic Severity: Add support for reading alert levels from monitoring tools' output (e.g., Nagios, Prometheus) to automatically determine the appropriate severity.

  • Log Aggregation: Integrate with popular logging systems like syslog or ELK stack to automatically send alerts based on log patterns.

  • Interactive Components: Extend the embed to include Discord buttons or select menus for acknowledging alerts or triggering automated responses.

// RELATED_SCRIPTS
โ† Back to DevLab valtersit.com