Webhooks are HTTP callbacks: one system POSTs to another when an event occurs. They look simple but fail in predictable ways under retries and network partitions.
Patterns
- Idempotency keys, deduplicate processing when the sender retries.
- Fast 2xx, async work, acknowledge quickly, process in a queue.
- Signature verification, validate HMAC or mTLS before acting on payload.
- Dead-letter queues isolate poison messages without blocking the stream.
- Exponential backoff on the receiver when downstream dependencies fail.
async function handleWebhook(req, res) {
const eventId = req.headers["x-event-id"];
if (await inbox.has(eventId)) {
return res.status(200).send("ok");
}
res.status(200).send("ok");
await queue.enqueue({ eventId, body: req.body });
}async function handleWebhook(req: Request, res: Response) {
const eventId = req.headers["x-event-id"] as string;
if (await inbox.has(eventId)) {
return res.status(200).send("ok");
}
res.status(200).send("ok");
await queue.enqueue({ eventId, body: req.body });
}public async Task<IActionResult> HandleWebhook(HttpRequest request)
{
var eventId = request.Headers["X-Event-Id"].ToString();
if (await _inbox.HasAsync(eventId))
return Ok();
Response.StatusCode = 200;
await _queue.EnqueueAsync(new WebhookJob(eventId, await ReadBodyAsync(request)));
return Ok();
}async def handle_webhook(request):
event_id = request.headers.get("x-event-id")
if await inbox.has(event_id):
return Response(status_code=200)
# Fast ack, async processing
await queue.enqueue({"event_id": event_id, "body": await request.body()})
return Response(status_code=200)Design for at-least-once delivery and idempotent handlers. The inbox pattern gives durable deduplication inside your database.
