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
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 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.