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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Validate structure and authenticate the sender before trusting any part of a webhook request.
- 2Returning distinct HTTP status codes lets the caller know whether to retry, resend, or give up.
- 3Enqueuing work rather than processing it inline keeps webhook endpoints fast and resilient.
Related explainers
php
<?php namespace App\Http\Controllers\Api;
Building a cursor-paginated feed in Laravel
cursor-pagination
eager-loading
validation
Intermediate
8 steps
go
package config import "time"
The functional options pattern in Go
functional-options
closures
immutability
Intermediate
7 steps
java
@Controller @RequestMapping("/employees") public class EmployeeController {
Customizing form binding in a Spring MVC controller
data-binding
validation
type-conversion
Intermediate
9 steps
go
package admin import ( "net/http"
Building a protected admin area in Gin
routing
middleware
authentication
Intermediate
6 steps
ruby
require "phonelib" class PhoneNumber class InvalidNumber < StandardError; end
Wrapping phone parsing in a Ruby value object
value-object
memoization
validation
Intermediate
7 steps
go
package middleware import ( "errors"
Capping request body size in Gin
middleware
request limits
error handling
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/handling-batched-webhooks-in-gin-explained-go-8c8b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.