Webhook Retry Service
Build a webhook delivery service in C# with ASP.NET Core. Other applications send it a job (a target URL and a JSON payload), and the service delivers it by POSTing the payload to that URL. If delivery fails with a temporary error, such as a timeout or a 503, it retries later with increasing delays (exponential backoff) until it succeeds or gives up. Every attempt is recorded, so you can see exactly what happened to each webhook. You'll learn ASP.NET Core minimal APIs, background workers with BackgroundService, HttpClient with timeouts, System.Text.Json, persistence with Entity Framework Core and SQLite, and how to design retry logic that survives restarts.
- Estimate
- ~13.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
ASP.NET Core · System.Text.Json
Project roadmap
- 01
Set up the API
~1.5hCreate an ASP.NET Core minimal API project (.NET 8 or newer). Add POST /jobs, which accepts JSON with a target URL and a payload (any JSON object), validates that the URL is absolute http or https and the payload isn't empty, and returns 202 Accepted with a new job id. Return 400 with a clear message for invalid input. Done when: POST /jobs returns an id for valid input and 400 for invalid input.
- 02
Store jobs and attempts
~2hAdd Entity Framework Core with SQLite. Create a Job entity (id, target URL, payload stored as a JSON string, status: Pending, Delivered or Failed, attempt count, next attempt time, created time) and a DeliveryAttempt entity (job id, time, HTTP status or error, duration). Save new jobs as Pending with the next attempt time set to now. Done when: created jobs are saved in the database and survive a restart.
- 03
Deliver jobs in the background
~2.5hWrite a BackgroundService that every 2 seconds loads up to 10 Pending jobs whose next attempt time has passed, and POSTs each payload with an IHttpClientFactory client that has a 10-second timeout. A 2xx response marks the job Delivered. Record every attempt as a DeliveryAttempt. Done when: a job sent to a working test URL (for example webhook.site) is delivered and its attempt is recorded.
- 04
Retry with exponential backoff
~2.5hTreat timeouts, network errors, 429 and 5xx responses as temporary: schedule another attempt after 2^attempts × 5 seconds plus a little random jitter, up to 8 attempts, then mark the job Failed. Treat other 4xx responses as permanent and mark the job Failed right away. Done when: a job to a URL that always returns 503 is retried with growing delays and ends as Failed.
- 05
Add status endpoints
~1.5hAdd GET /jobs/{id} returning the job's status, attempt count and all its attempts, and GET /jobs?status=Failed listing jobs by status. Add POST /jobs/{id}/retry, which puts a Failed job back to Pending. Done when: a job's full delivery history is visible through the API.
- 06
Make it safe to restart
~1.5hMake sure a job is never sent twice at the same time: mark jobs as InProgress when the worker picks them up, and on startup reset InProgress jobs back to Pending, since they were interrupted. Send an Idempotency-Key header with the job id so receivers can ignore duplicate deliveries. Done when: stopping and restarting the service mid-delivery doesn't lose or duplicate jobs.
- 07
Write tests
~2hWrite xUnit tests with WebApplicationFactory and a fake HttpMessageHandler that simulates the target server: a successful delivery, a 503 followed by a success, a permanent 400, and the backoff delays. Keep the retry rules in their own class so they can be tested without time passing. Done when: dotnet test passes.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack