Quick Start: C# Email Webhook
JsonHook delivers every inbound email as a JSON POST request to your webhook endpoint. Setting up a C# handler takes less than 5 minutes. Start by initializing your project:
dotnet new web -n WebhookApp && cd WebhookApp
Then create your webhook endpoint. The following example shows the minimal code needed to receive and acknowledge a JsonHook delivery:
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/webhook", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var payload = JsonSerializer.Deserialize(body);
var from = payload.GetProperty("email").GetProperty("from").GetString();
var subject = payload.GetProperty("email").GetProperty("subject").GetString();
Console.WriteLine($"Email from: {from} | Subject: {subject}");
return Results.Ok();
});
app.Run("http://0.0.0.0:3000");
Point your JsonHook address webhook URL to this endpoint and you will start receiving parsed emails as JSON within seconds of the email arriving.
Full C# Implementation
The quick start example above is enough to get started, but a production implementation should include signature verification, structured error handling, and proper HTTP response codes. The complete example below demonstrates all of these patterns together.
This implementation verifies the X-JsonHook-Signature header to confirm the request genuinely came from JsonHook, parses the full email payload, and returns the appropriate HTTP status codes to trigger or suppress retries.
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var webhookSecret = Environment.GetEnvironmentVariable("JSONHOOK_WEBHOOK_SECRET")
?? throw new Exception("JSONHOOK_WEBHOOK_SECRET not set");
bool VerifySignature(byte[] body, string? sigHeader)
{
if (string.IsNullOrEmpty(sigHeader)) return false;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSecret));
var computed = Convert.ToHexString(hmac.ComputeHash(body)).ToLowerInvariant();
// CryptographicOperations.FixedTimeEquals is constant-time
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(computed),
Encoding.UTF8.GetBytes(sigHeader)
);
}
app.MapPost("/webhook", async (HttpRequest request) =>
{
using var ms = new MemoryStream();
await request.Body.CopyToAsync(ms);
var body = ms.ToArray();
var signature = request.Headers["X-JsonHook-Signature"].FirstOrDefault();
if (!VerifySignature(body, signature))
{
return Results.Unauthorized();
}
JsonElement payload;
try { payload = JsonSerializer.Deserialize(body); }
catch { return Results.BadRequest(); }
var email = payload.GetProperty("email");
var timestamp = payload.GetProperty("timestamp").GetString();
var address = payload.GetProperty("address").GetString();
var from = email.GetProperty("from").GetString();
var subject = email.GetProperty("subject").GetString();
var textBody = email.GetProperty("textBody").GetString();
Console.WriteLine($"[{timestamp}] Email at {address} from {from}: {subject}");
if (email.TryGetProperty("attachments", out var attachments))
{
foreach (var att in attachments.EnumerateArray())
{
Console.WriteLine($" Attachment: {att.GetProperty("filename")} " +
$"({att.GetProperty("size").GetInt32()} bytes)");
}
}
// Queue background processing here (e.g., Channel, BackgroundService, Hangfire)
return Results.Ok();
});
app.Run("http://0.0.0.0:3000");
The webhook handler returns 200 immediately after queuing the email for processing. Avoid doing expensive work (database writes, API calls) synchronously inside the handler — process the payload in a background job to stay within JsonHook's 10-second response timeout.
Build Your C# Email Integration
Free API key — start receiving webhooks in 5 minutes.
Get Free API KeyParsing the Webhook Payload
Every JsonHook delivery is an HTTP POST with Content-Type: application/json. The payload follows a consistent schema regardless of the originating email client or provider:
using System.Text.Json;
// body is byte[] read from the request stream
var payload = JsonSerializer.Deserialize(body);
var event_ = payload.GetProperty("event").GetString(); // "email.received"
var timestamp = payload.GetProperty("timestamp").GetString(); // ISO 8601
var address = payload.GetProperty("address").GetString(); // "[email protected]"
var email = payload.GetProperty("email");
var from = email.GetProperty("from").GetString();
var to = email.GetProperty("to").EnumerateArray()
.Select(x => x.GetString()).ToList();
var subject = email.GetProperty("subject").GetString();
var textBody = email.GetProperty("textBody").GetString();
var htmlBody = email.GetProperty("htmlBody").GetString();
foreach (var att in email.GetProperty("attachments").EnumerateArray())
{
var filename = att.GetProperty("filename").GetString();
var contentType = att.GetProperty("contentType").GetString();
var size = att.GetProperty("size").GetInt32();
}
Key fields in the payload:
- event — Always
"email.received"for inbound email events - timestamp — ISO 8601 timestamp of when JsonHook received the email
- address — The JsonHook inbound address that received the email (e.g.,
[email protected]) - email.from — Sender address string, e.g.,
"Alice <[email protected]>" - email.to — Array of recipient address strings
- email.subject — Email subject line
- email.textBody — Plain text body of the email (may be empty if HTML-only)
- email.htmlBody — HTML body of the email (may be empty if plain-text-only)
- email.attachments — Array of attachment objects, each with
filename,contentType,size, andcontentId
Verifying Webhook Signatures
JsonHook signs every webhook delivery using HMAC-SHA256. The signature is included in the X-JsonHook-Signature request header as a hex digest. To verify it, compute the HMAC-SHA256 of the raw request body using your address's webhook secret and compare it to the header value.
Your webhook secret is returned when you create an inbound address via the API (POST /api/addresses). Store it as an environment variable — never hard-code it.
using System.Security.Cryptography;
using System.Text;
bool VerifyJsonHookSignature(byte[] body, string? sigHeader, string secret)
{
if (string.IsNullOrEmpty(sigHeader)) return false;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var computed = Convert.ToHexString(hmac.ComputeHash(body)).ToLowerInvariant();
// CryptographicOperations.FixedTimeEquals prevents timing attacks
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(computed),
Encoding.UTF8.GetBytes(sigHeader)
);
}
Always verify the signature before processing the payload. Return 401 for invalid signatures so that legitimate retries from JsonHook (which always include a valid signature) are distinguishable from spoofed requests.
Error Handling Best Practices
Reliable webhook handling requires careful attention to error responses. JsonHook uses your HTTP response code to decide whether to retry a delivery:
- Return 200 quickly: Acknowledge receipt immediately and process asynchronously. JsonHook will retry any non-2xx response.
- Return 400 for bad requests: If the payload fails your own validation (not signature — use 401 for that), return 400 to prevent retries of malformed deliveries.
- Return 500 to trigger retries: If your downstream system is temporarily unavailable, returning 500 causes JsonHook to retry with exponential backoff (up to 5 attempts over ~1 hour).
- Never return 200 before verifying the signature: Doing so silently accepts spoofed requests.
C# ecosystem tips:
- Use
CryptographicOperations.FixedTimeEquals()(available in .NET 5+) rather than string equality for signature comparison - Copy the request body to a
MemoryStreambefore signature verification — ASP.NET Core may not allow re-reading the body stream after it has been consumed - Use
System.Threading.Channelsor aBackgroundServiceto process email payloads off the request thread; returnResults.Ok()before doing any heavy work - Register a global exception handler with
app.UseExceptionHandlerto return 500 on unhandled exceptions, which causes JsonHook to retry
C# Ecosystem Tips
The C# ecosystem offers several libraries and patterns that pair well with JsonHook webhook handling. Here are general recommendations:
- Use a well-maintained HTTP server library appropriate for your use case — the examples in this guide use the most common choice, but any library that gives you raw body access works.
- Store your webhook secret in an environment variable and load it via your language's standard env access pattern — never commit secrets to version control.
- Use your language's standard HMAC library rather than a third-party package — all languages featured in this guide have HMAC-SHA256 in their standard library.
- Consider a structured logging library to capture the
address,event, andtimestampfields from every webhook delivery for observability. - Test your handler locally using a tunneling tool like ngrok or a local webhook testing service before pointing your JsonHook address at a production URL.