Build a Go Webhook Handler for Identity Verification Events
Learn to build a robust webhook handler in Go for real-time identity verification events. This guide covers setting up an HTTP server, processing JSON payloads, handling retries, and ensuring security for your verification.

Real-time AutomationWebhooks enable instant, event-driven responses to identity verification outcomes, crucial for dynamic user onboarding and fraud prevention systems.
Robust Error HandlingImplementing retry mechanisms, dead-letter queues, and comprehensive logging is vital for maintaining data integrity and system reliability when processing asynchronous webhook events.
Security is ParamountAlways validate webhook signatures, use HTTPS, and sanitize incoming data to protect against tampering and unauthorized access, safeguarding sensitive identity information.
Didit Simplifies IntegrationDidit's modular, API-first platform provides comprehensive webhook support, allowing developers to easily configure real-time notifications for all identity verification events, from ID Verification to AML Screening, with Free Core KYC.
In the modern landscape of digital identity, real-time feedback is not just a luxury; it's a necessity. Whether you're onboarding new users, preventing fraud, or ensuring compliance, knowing the outcome of an identity verification check the moment it happens allows for immediate action and a smoother user experience. This is where webhooks shine. Webhooks provide a powerful mechanism for identity verification platforms to notify your application about events as they occur, eliminating the need for constant polling.
This blog post will guide you through building a robust and secure webhook handler in Go, specifically tailored for processing identity verification events. Go's strong concurrency features and performance make it an excellent choice for handling the asynchronous nature of webhooks.
Understanding Webhooks for Identity Verification
Before diving into code, let's clarify what webhooks are and why they are critical for identity verification. A webhook is essentially a user-defined HTTP callback. Instead of your application continuously asking an identity verification service for updates (polling), the service sends an HTTP POST request to a URL you provide whenever a specific event happens. For identity verification, these events could include:
- A user's ID document scan is complete.
- Liveness detection passes or fails.
- AML screening returns a match.
- A full verification workflow reaches a final status (e.g., approved, rejected, manual review).
Receiving these events in real-time allows your application to update user statuses, trigger downstream processes, or notify administrators without delay. For example, once a user's ID Verification and Passive & Active Liveness checks are approved, you can immediately grant them access to your service.
Setting Up Your Go Webhook Server
Building a webhook handler in Go involves setting up a simple HTTP server that listens for incoming POST requests. We'll use Go's standard net/http package for this. First, let's create a basic server structure.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
// WebhookPayload represents the structure of an incoming webhook from an identity verification service.
// This is a simplified example; actual payloads will vary.
type WebhookPayload struct {
Event string `json:"event"`
SessionID string `json:"session_id"`
Status string `json:"status"`
Data json.RawMessage `json:"data"` // Use RawMessage to defer unmarshaling of nested data
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
var payload WebhookPayload
err = json.Unmarshal(body, &payload)
if err != nil {
http.Error(w, "Error unmarshaling JSON payload", http.StatusBadRequest)
log.Printf("Failed to unmarshal webhook: %v, Body: %s", err, body)
return
}
// Log the received event for now. In a real application, you'd process this.
log.Printf("Received webhook event: %s for session %s with status %s", payload.Event, payload.SessionID, payload.Status)
// Respond with a 200 OK to acknowledge receipt. Most webhook senders expect this.
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Webhook received successfully")
}
func main() {
http.HandleFunc("/webhook", webhookHandler)
port := ":8080"
log.Printf("Webhook server starting on port %s\n", port)
log.Fatal(http.ListenAndServe(port, nil))
}
This basic example sets up an HTTP server listening on port 8080 and handles POST requests to the /webhook endpoint. It reads the incoming JSON payload, unmarshals it into a WebhookPayload struct, and logs the event. Crucially, it responds with a 200 OK status code to confirm successful receipt of the webhook. Failing to respond with a 200 OK can cause the sender to retry the delivery, leading to duplicate processing.
Ensuring Security: Signature Verification and HTTPS
Security is paramount when dealing with sensitive identity verification data. You must ensure that incoming webhooks are legitimate and haven't been tampered with. The two primary mechanisms for this are:
- HTTPS: Always expose your webhook endpoint over HTTPS to encrypt the data in transit, preventing eavesdropping.
- Signature Verification: Most reputable identity verification providers, including Didit, send a signature or hash in the request headers (e.g.,
X-Didit-Signature). You should use a shared secret key to compute your own hash of the raw request body and compare it with the incoming signature. If they don't match, the webhook is likely fraudulent or compromised.
Here's an example of how you might add signature verification to your handler:
// ... (previous imports and WebhookPayload struct)
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
// ... other imports
)
const webhookSecret = "YOUR_DIDIT_WEBHOOK_SECRET" // Replace with your actual secret
func verifySignature(body []byte, signature string) bool {
hmacHash := hmac.New(sha256.New, []byte(webhookSecret))
hmacHash.Write(body)
expectedMAC := hmacHash.Sum(nil)
decodedSignature, err := hex.DecodeString(signature)
if err != nil {
return false
}
return hmac.Equal(decodedSignature, expectedMAC)
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// ... (method check and body reading)
signature := r.Header.Get("X-Didit-Signature") // Or similar header name from your provider
if signature == "" {
http.Error(w, "Missing signature header", http.StatusUnauthorized)
return
}
if !verifySignature(body, signature) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// ... (payload unmarshaling and processing)
}
Remember to store your webhookSecret securely, ideally in environment variables or a secret management system, not hardcoded in your application.
Robustness: Asynchronous Processing and Retries
Webhooks should be processed quickly to avoid timeouts and retries from the sender. For complex or time-consuming tasks, it's best to offload processing to a separate goroutine or message queue. Your webhook handler should primarily focus on receiving, validating, and acknowledging the event.
// ... (previous code)
func processWebhookAsync(payload WebhookPayload) {
// In a real application, this might involve:
// - Storing the event in a database
// - Pushing to a message queue (e.g., Kafka, RabbitMQ)
// - Calling other internal services
log.Printf("Asynchronously processing event: %s for session %s", payload.Event, payload.SessionID)
// Simulate work
// time.Sleep(5 * time.Second)
log.Printf("Finished async processing for session %s", payload.SessionID)
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// ... (signature verification and payload unmarshaling)
// Acknowledge receipt immediately.
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Webhook received successfully")
// Process asynchronously to avoid blocking the HTTP response.
go processWebhookAsync(payload)
}
Furthermore, external services may occasionally fail. Implement retry mechanisms for any downstream calls made during asynchronous processing. Consider using a dead-letter queue (DLQ) for events that repeatedly fail, allowing manual inspection and reprocessing.
How Didit Helps
Didit, as an AI-native, developer-first identity platform, makes integrating real-time identity verification events straightforward and secure. Didit's modular architecture is built for orchestration, meaning you can define complex workflows combining ID Verification (OCR, MRZ, barcodes), Passive & Active Liveness, 1:1 Face Match, AML Screening & Monitoring, Proof of Address, and even privacy-preserving Age Estimation. Each step of these workflows, and the final outcome, can trigger webhook events directly to your Go application.
With Didit, you configure your webhook URL in the Business Console, and Didit sends automated updates to your configured endpoint as the user progresses and when the final verification result is ready. Didit provides robust webhook payloads, often including a signature header for you to verify authenticity, ensuring the security of your integration. This allows you to build sophisticated, automated responses to verification outcomes, accelerating user onboarding, enhancing fraud detection, and streamlining compliance processes without manual intervention.
Didit's advantages, such as Free Core KYC and no setup fees, coupled with its clean APIs and comprehensive documentation, empower you to build powerful, event-driven identity solutions with maximum efficiency. Whether you're using Verification Links for no-code integration or directly leveraging the API, Didit's webhook system ensures you're always in sync with your users' verification journeys.
Ready to Get Started?
Ready to see Didit in action? Get a free demo today.
Start verifying identities for free with Didit's free tier.