go 46 lines · 7 steps

Handling batched webhooks in Gin

A Gin handler that validates, authenticates, and enqueues a batch of webhook events for asynchronous processing.

Explained by highlit
1func (h *WebhookHandler) HandleBatch(c *gin.Context) {
2 var payload BatchWebhookPayload
3 if err := c.ShouldBindJSON(&payload); err != nil {
4 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
5 return
6 }
7 
8 sig := c.GetHeader("X-Signature-256")
9 if !h.verifier.Valid(sig, payload.Raw) {
10 c.JSON(http.StatusUnauthorized, gin.H{"error": "signature mismatch"})
11 return
12 }
13 
14 if len(payload.Events) == 0 {
15 c.JSON(http.StatusAccepted, gin.H{"accepted": 0, "batch_id": payload.BatchID})
16 return
17 }
18 
19 batchID := payload.BatchID
20 receivedAt := time.Now().UTC()
21 
22 for _, ev := range payload.Events {
23 job := WebhookJob{
24 BatchID: batchID,
25 EventID: ev.ID,
26 Type: ev.Type,
27 Data: ev.Data,
28 ReceivedAt: receivedAt,
29 }
30 if err := h.dispatcher.Enqueue(c.Request.Context(), job); err != nil {
31 h.logger.Error("enqueue failed",
32 zap.String("batch_id", batchID),
33 zap.String("event_id", ev.ID),
34 zap.Error(err),
35 )
36 c.JSON(http.StatusServiceUnavailable, gin.H{"error": "unable to accept batch"})
37 return
38 }
39 }
40 
41 c.JSON(http.StatusAccepted, gin.H{
42 "batch_id": batchID,
43 "accepted": len(payload.Events),
44 "status": "queued",
45 })
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Validate structure and authenticate the sender before trusting any part of a webhook request.
  2. 2Returning distinct HTTP status codes lets the caller know whether to retry, resend, or give up.
  3. 3Enqueuing work rather than processing it inline keeps webhook endpoints fast and resilient.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Handling batched webhooks in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code