Skip to main content
All articles
· Updated February 15, 2026

Send Discord Webhook Messages with C# (.NET)

Learn how to send Discord webhook messages using C# and .NET. Complete guide with HttpClient examples, embed formatting, error handling, and production patterns.

csharpdotnetwebhooktutorialcsharp discord 2026dotnet discordaspnet discord
Send Discord Webhook Messages with C# (.NET)

Discord webhooks provide a straightforward way to send automated messages to your Discord channels from .NET applications. Whether you’re building monitoring systems, CI/CD notifications, or game server alerts, C# offers powerful tools to integrate with Discord’s webhook API.

This guide covers everything from basic message sending to production-ready patterns with proper error handling and dependency injection.

Prerequisites

You’ll need:

  • .NET 6.0 or later
  • A Discord webhook URL (create one in your server’s channel settings)
  • Basic familiarity with async/await in C#

Basic Webhook Message with HttpClient

The simplest way to send a Discord webhook message uses HttpClient with a JSON payload:

using System.Net.Http;
using System.Text;
using System.Text.Json;

public class DiscordWebhook
{
    private readonly HttpClient _httpClient;
    private readonly string _webhookUrl;

    public DiscordWebhook(string webhookUrl)
    {
        _webhookUrl = webhookUrl;
        _httpClient = new HttpClient();
    }

    public async Task SendMessageAsync(string content)
    {
        var payload = new
        {
            content = content
        };

        var json = JsonSerializer.Serialize(payload);
        var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

        var response = await _httpClient.PostAsync(_webhookUrl, httpContent);
        response.EnsureSuccessStatusCode();
    }
}

Usage:

var webhook = new DiscordWebhook("https://discord.com/api/webhooks/YOUR_WEBHOOK_URL");
await webhook.SendMessageAsync("Hello from C#!");

This sends a plain text message. For more sophisticated notifications, you’ll want to use embeds.

Creating Rich Embeds

Discord embeds let you send formatted messages with colors, fields, images, and timestamps. Here’s a complete embed implementation:

public class DiscordEmbed
{
    public string? Title { get; set; }
    public string? Description { get; set; }
    public int? Color { get; set; }
    public List<EmbedField>? Fields { get; set; }
    public EmbedFooter? Footer { get; set; }
    public string? Timestamp { get; set; }
    public EmbedThumbnail? Thumbnail { get; set; }
    public EmbedImage? Image { get; set; }
}

public class EmbedField
{
    public string Name { get; set; } = string.Empty;
    public string Value { get; set; } = string.Empty;
    public bool Inline { get; set; }
}

public class EmbedFooter
{
    public string Text { get; set; } = string.Empty;
    public string? IconUrl { get; set; }
}

public class EmbedThumbnail
{
    public string Url { get; set; } = string.Empty;
}

public class EmbedImage
{
    public string Url { get; set; } = string.Empty;
}

Now update the webhook class to support embeds:

public async Task SendEmbedAsync(string? content, params DiscordEmbed[] embeds)
{
    var payload = new
    {
        content = content,
        embeds = embeds
    };

    var options = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
    };

    var json = JsonSerializer.Serialize(payload, options);
    var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

    var response = await _httpClient.PostAsync(_webhookUrl, httpContent);
    response.EnsureSuccessStatusCode();
}

Example usage with a deployment notification:

var embed = new DiscordEmbed
{
    Title = "Deployment Successful",
    Description = "Application deployed to production",
    Color = 0x00FF00, // Green
    Fields = new List<EmbedField>
    {
        new EmbedField { Name = "Environment", Value = "Production", Inline = true },
        new EmbedField { Name = "Version", Value = "v2.4.1", Inline = true },
        new EmbedField { Name = "Duration", Value = "3m 42s", Inline = true }
    },
    Footer = new EmbedFooter { Text = "CI/CD Pipeline" },
    Timestamp = DateTime.UtcNow.ToString("o")
};

await webhook.SendEmbedAsync(null, embed);

For a complete guide on designing beautiful embeds, check out our Discord embed formatting guide.

Sending File Attachments

To send files alongside your message, use MultipartFormDataContent:

public async Task SendFileAsync(string content, string filePath)
{
    using var form = new MultipartFormDataContent();
    
    // Add JSON payload
    var payload = new { content = content };
    var json = JsonSerializer.Serialize(payload);
    form.Add(new StringContent(json, Encoding.UTF8, "application/json"), "payload_json");
    
    // Add file
    var fileStream = File.OpenRead(filePath);
    var fileName = Path.GetFileName(filePath);
    form.Add(new StreamContent(fileStream), "file", fileName);
    
    var response = await _httpClient.PostAsync(_webhookUrl, form);
    response.EnsureSuccessStatusCode();
}

Usage:

await webhook.SendFileAsync("Here's the error log:", "logs/error.txt");

Error Handling and Retry Logic

Production applications need proper error handling. Discord webhooks can fail due to network issues, rate limits, or invalid payloads:

public async Task<bool> SendMessageWithRetryAsync(string content, int maxRetries = 3)
{
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            var payload = new { content = content };
            var json = JsonSerializer.Serialize(payload);
            var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync(_webhookUrl, httpContent);

            if (response.IsSuccessStatusCode)
            {
                return true;
            }

            // Handle rate limiting
            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                var retryAfter = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(5);
                await Task.Delay(retryAfter);
                continue;
            }

            // Log error and retry
            var errorBody = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"Webhook failed (attempt {attempt + 1}): {response.StatusCode} - {errorBody}");
            
            if (attempt < maxRetries - 1)
            {
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
            }
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"Network error (attempt {attempt + 1}): {ex.Message}");
            
            if (attempt < maxRetries - 1)
            {
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
            }
        }
    }

    return false;
}

This implementation:

  • Retries up to 3 times with exponential backoff
  • Respects Discord’s rate limit headers
  • Logs errors for debugging
  • Returns success/failure status

Dependency Injection for ASP.NET Core

For ASP.NET Core applications, register the webhook service in your dependency injection container:

// IDiscordWebhookService.cs
public interface IDiscordWebhookService
{
    Task SendMessageAsync(string content);
    Task SendEmbedAsync(string? content, params DiscordEmbed[] embeds);
}

// DiscordWebhookService.cs
public class DiscordWebhookService : IDiscordWebhookService
{
    private readonly HttpClient _httpClient;
    private readonly string _webhookUrl;
    private readonly ILogger<DiscordWebhookService> _logger;

    public DiscordWebhookService(
        HttpClient httpClient,
        IConfiguration configuration,
        ILogger<DiscordWebhookService> logger)
    {
        _httpClient = httpClient;
        _webhookUrl = configuration["Discord:WebhookUrl"] 
            ?? throw new ArgumentNullException("Discord webhook URL not configured");
        _logger = logger;
    }

    public async Task SendMessageAsync(string content)
    {
        try
        {
            var payload = new { content = content };
            var json = JsonSerializer.Serialize(payload);
            var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync(_webhookUrl, httpContent);
            response.EnsureSuccessStatusCode();
            
            _logger.LogInformation("Discord message sent successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send Discord message");
            throw;
        }
    }

    public async Task SendEmbedAsync(string? content, params DiscordEmbed[] embeds)
    {
        // Implementation similar to above
    }
}

Register in Program.cs:

builder.Services.AddHttpClient<IDiscordWebhookService, DiscordWebhookService>();

Add to appsettings.json:

{
  "Discord": {
    "WebhookUrl": "https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
  }
}

Now inject and use anywhere in your application:

public class OrderController : ControllerBase
{
    private readonly IDiscordWebhookService _discord;

    public OrderController(IDiscordWebhookService discord)
    {
        _discord = discord;
    }

    [HttpPost]
    public async Task<IActionResult> CreateOrder(Order order)
    {
        // Process order...
        
        await _discord.SendMessageAsync($"New order #{order.Id} received!");
        
        return Ok();
    }
}

Real-World Example: Service Health Monitoring

Here’s a complete example that monitors service health and sends formatted notifications to Discord:

public class HealthMonitorService : BackgroundService
{
    private readonly IDiscordWebhookService _discord;
    private readonly ILogger<HealthMonitorService> _logger;
    private readonly HttpClient _httpClient;

    public HealthMonitorService(
        IDiscordWebhookService discord,
        ILogger<HealthMonitorService> logger,
        IHttpClientFactory httpClientFactory)
    {
        _discord = discord;
        _logger = logger;
        _httpClient = httpClientFactory.CreateClient();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await CheckServiceHealthAsync();
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private async Task CheckServiceHealthAsync()
    {
        var services = new[]
        {
            ("API", "https://api.example.com/health"),
            ("Database", "https://db.example.com/health"),
            ("Cache", "https://cache.example.com/health")
        };

        var fields = new List<EmbedField>();
        var allHealthy = true;

        foreach (var (name, url) in services)
        {
            try
            {
                var response = await _httpClient.GetAsync(url);
                var isHealthy = response.IsSuccessStatusCode;
                
                fields.Add(new EmbedField
                {
                    Name = name,
                    Value = isHealthy ? "✓ Healthy" : "✗ Unhealthy",
                    Inline = true
                });

                if (!isHealthy) allHealthy = false;
            }
            catch (Exception ex)
            {
                fields.Add(new EmbedField
                {
                    Name = name,
                    Value = $"✗ Error: {ex.Message}",
                    Inline = true
                });
                allHealthy = false;
            }
        }

        var embed = new DiscordEmbed
        {
            Title = allHealthy ? "All Services Healthy" : "Service Health Alert",
            Description = allHealthy 
                ? "All monitored services are operational" 
                : "One or more services are experiencing issues",
            Color = allHealthy ? 0x00FF00 : 0xFF0000,
            Fields = fields,
            Footer = new EmbedFooter { Text = "Health Monitor" },
            Timestamp = DateTime.UtcNow.ToString("o")
        };

        if (!allHealthy)
        {
            await _discord.SendEmbedAsync("@here Service health check failed!", embed);
        }
    }
}

Register the background service:

builder.Services.AddHostedService<HealthMonitorService>();

Best Practices

  1. Use HttpClientFactory: Never create HttpClient instances directly. Use IHttpClientFactory to avoid socket exhaustion.

  2. Store webhook URLs securely: Use configuration files, environment variables, or Azure Key Vault—never hardcode URLs.

  3. Implement rate limiting: Discord allows 30 requests per minute per webhook. Queue messages if you exceed this.

  4. Validate payloads: Ensure your JSON is valid before sending. Use strongly-typed classes and serialization.

  5. Handle failures gracefully: Don’t let webhook failures crash your application. Log errors and continue.

  6. Use structured logging: Include context in your logs to debug webhook issues.

Testing Your Webhooks

Before deploying, test your webhook implementation:

[Fact]
public async Task SendMessage_ValidPayload_ReturnsSuccess()
{
    var mockHttp = new MockHttpMessageHandler();
    mockHttp.When("https://discord.com/api/webhooks/*")
            .Respond(HttpStatusCode.NoContent);

    var client = mockHttp.ToHttpClient();
    var webhook = new DiscordWebhook("https://discord.com/api/webhooks/test", client);

    var result = await webhook.SendMessageWithRetryAsync("Test message");

    Assert.True(result);
}

For quick testing without writing code, use our visual webhook builder to design and send messages instantly.

Next Steps

You now have a complete foundation for sending Discord webhooks from C#. Consider exploring:

  • Building custom embed templates for different event types
  • Integrating webhooks into your CI/CD pipeline
  • Creating a centralized notification service for microservices
  • Adding message queuing for high-volume scenarios

The patterns shown here scale from simple scripts to enterprise applications. Start simple, then add complexity as your needs grow.

discord-webhook.com also offers scheduled messages, thread and forum support, polls, and interactive buttons with actions — all configurable through the visual builder without writing code.