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.
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
-
Use HttpClientFactory: Never create
HttpClientinstances directly. UseIHttpClientFactoryto avoid socket exhaustion. -
Store webhook URLs securely: Use configuration files, environment variables, or Azure Key Vault—never hardcode URLs.
-
Implement rate limiting: Discord allows 30 requests per minute per webhook. Queue messages if you exceed this.
-
Validate payloads: Ensure your JSON is valid before sending. Use strongly-typed classes and serialization.
-
Handle failures gracefully: Don’t let webhook failures crash your application. Log errors and continue.
-
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.
Related Articles
- Discord Webhook Embed Guide
- Discord Webhook Colors Reference
- Discord Webhook Notifications Best Practices
- Send Discord Webhooks with Python
- Discord Webhook Scheduled Messages — Automate recurring messages and timed notifications with scheduled webhooks
- Interactive Buttons and Actions — Add clickable buttons and action rows to your Discord webhook messages
Try it in our tool
Open Discord Webhook Builder