go 50 lines · 9 steps

Verifying and processing webhooks in Gin

A Gin handler that authenticates a webhook with HMAC, deduplicates it, and queues it for async processing.

Explained by highlit
1package handlers
2 
3type WebhookEvent struct {
4 ID string `json:"id"`
5 Type string `json:"type"`
6 Data json.RawMessage `json:"data"`
7}
8 
9func VerifySignature(secret, signature string, body []byte) bool {
10 mac := hmac.New(sha256.New, []byte(secret))
11 mac.Write(body)
12 expected := hex.EncodeToString(mac.Sum(nil))
13 return hmac.Equal([]byte(expected), []byte(signature))
14}
15 
16func (h *Handler) ReceiveWebhook(c *gin.Context) {
17 body, err := c.GetRawData()
18 if err != nil {
19 c.JSON(http.StatusBadRequest, gin.H{"error": "cannot read body"})
20 return
21 }
22 c.Set(gin.BodyBytesKey, body)
23 
24 signature := c.GetHeader("X-Signature-256")
25 if !VerifySignature(h.secret, signature, body) {
26 c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
27 return
28 }
29 
30 var event WebhookEvent
31 if err := c.ShouldBindBodyWith(&event, binding.JSON); err != nil {
32 c.JSON(http.StatusBadRequest, gin.H{"error": "malformed payload"})
33 return
34 }
35 
36 if ok, err := h.dedup.Add(c.Request.Context(), event.ID); err != nil {
37 c.JSON(http.StatusServiceUnavailable, gin.H{"error": "dedup unavailable"})
38 return
39 } else if !ok {
40 c.JSON(http.StatusOK, gin.H{"status": "duplicate"})
41 return
42 }
43 
44 if err := h.queue.Enqueue(c.Request.Context(), event.Type, body); err != nil {
45 c.JSON(http.StatusServiceUnavailable, gin.H{"error": "queue unavailable"})
46 return
47 }
48 
49 c.JSON(http.StatusOK, gin.H{"status": "accepted", "id": event.ID})
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Verify HMAC signatures against the exact raw bytes you received, before parsing anything.
  2. 2Deduplicate by event ID so retried webhooks are safely idempotent.
  3. 3Return distinct status codes for each failure mode so senders know whether to retry.

Related explainers

Share this explainer

Here's the card — post it anywhere.

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