Skip to main content
Didit Raises $7.5M to Build the Infrastructure for Identity and Fraud
Didit
Back to blog
Blog · March 6, 2026

Dynamic Rate Limiting & Circuit Breakers for Resilient Identity APIs in Go

Building resilient identity verification APIs is crucial. This post explores implementing dynamic rate limiting and circuit breakers in Go to protect your services from overload and cascading failures.

By DiditUpdated
dynamic-rate-limiting-circuit-breakers-for-resilient-identity-apis-in-go.png

Protect Your Identity APIsImplementing dynamic rate limiting and circuit breakers is essential for safeguarding identity verification APIs against abuse, overload, and cascading failures, ensuring stability and reliability.

Go for Performance and ConcurrencyGo offers excellent concurrency primitives and performance, making it an ideal language for building robust and efficient microservices that require sophisticated resilience patterns.

Strategic Implementation is KeyEffective implementation requires careful consideration of algorithms (e.g., token bucket for rate limiting), monitoring, and configuration to balance protection with legitimate user experience.

Didit Simplifies ResilienceDidit inherently provides a highly resilient, globally distributed identity verification platform, meaning you don't have to build complex rate limiting and circuit breaker logic from scratch for your core KYC and identity workflows.

The Critical Need for Resilient Identity Verification APIs

Identity verification APIs are at the heart of many critical business processes, from user onboarding and financial transactions to age-gated content access. The reliability and availability of these APIs are paramount. A surge in traffic, a malicious attack, or an upstream service failure can quickly degrade performance, lead to service outages, and impact user trust. This is where resilience patterns like dynamic rate limiting and circuit breakers become indispensable, especially when building with a high-performance language like Go.

Imagine a scenario where your application relies on Didit's ID Verification for onboarding new users. If an attacker floods your system with requests, or if an internal component experiences a temporary slowdown, without proper safeguards, your entire onboarding process could grind to a halt. This not only frustrates legitimate users but can also incur significant costs and reputational damage. Implementing these patterns ensures that your system can gracefully handle such pressures, maintaining stability and a positive user experience.

Implementing Dynamic Rate Limiting in Go

Rate limiting controls the number of requests a client can make to a service within a given time window. Dynamic rate limiting adjusts these limits based on various factors, such as client reputation, service health, or current load. In Go, the token bucket algorithm is a popular and effective choice for implementing rate limiting.

Token Bucket Algorithm in Go

A token bucket has a fixed capacity and tokens are added to it at a constant rate. Each request consumes one token. If the bucket is empty, the request is either denied or queued. Go's standard library provides the golang.org/x/time/rate package, which simplifies this implementation.

Consider a scenario using Didit's Passive & Active Liveness checks. While Didit handles its own internal rate limiting, your application might want to limit the number of liveness requests per user to prevent abuse or control costs. Here's a basic example:

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"

	"golang.org/x/time/rate"
)

// clientLimiter holds a rate limiter for each client
type clientLimiter struct {
	limiters map[string]*rate.Limiter
	mu       sync.Mutex
	// Default rate: 10 requests per second with a burst of 20
	defaultLimit *rate.Limiter
}

func newClientLimiter() *clientLimiter {
	return &clientLimiter{
		limiters: make(map[string]*rate.Limiter),
		defaultLimit: rate.NewLimiter(rate.Every(time.Second/10), 20),
	}
}

func (cl *clientLimiter) GetLimiter(clientID string) *rate.Limiter {
	cl.mu.Lock()
	defer cl.mu.Unlock()

	limiter, exists := cl.limiters[clientID]
	if !exists {
		// In a real-world scenario, you might fetch specific limits for clientID from a DB
		// For dynamic limits, you'd adjust rate.Every and burst based on client tiers, etc.
		limiter = rate.NewLimiter(rate.Every(time.Second/5), 10) // Example: 5 req/sec, burst 10 for specific client
		cl.limiters[clientID] = limiter
	}
	return limiter
}

func rateLimitMiddleware(next http.Handler, cl *clientLimiter) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		clientID := r.Header.Get("X-Client-ID") // Or extract from API key, JWT, etc.
		limiter := cl.defaultLimit
		if clientID != "" {
			limiter = cl.GetLimiter(clientID)
		}

		if !limiter.Allow() {
			http.Error(w, "Too many requests", http.StatusTooManyRequests)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	clientLimiter := newClientLimiter()

	http.Handle("/verify", rateLimitMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Identity verification request processed!")
	}), clientLimiter))

	log.Println("Server starting on port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

This example demonstrates a basic dynamic rate limiter where different clients can have different limits. For more sophisticated dynamic adjustments, you'd integrate with a configuration service or a monitoring system to update limiter parameters in real-time. For services like AML Screening & Monitoring, where compliance is critical, precise rate limiting can prevent service disruptions that might lead to regulatory non-compliance.

Implementing Circuit Breakers in Go

Circuit breakers prevent cascading failures in distributed systems. When a service repeatedly fails, the circuit breaker "trips," preventing further requests from being sent to the failing service for a period. This gives the downstream service time to recover and prevents the upstream service from wasting resources on doomed requests.

Circuit Breaker States: Closed, Open, Half-Open

  • Closed: Requests are allowed to pass through to the service. If failures exceed a threshold, it trips to Open.
  • Open: Requests are immediately rejected without calling the service. After a timeout, it transitions to Half-Open.
  • Half-Open: A limited number of test requests are allowed. If these succeed, it transitions back to Closed; otherwise, it returns to Open.

Several Go libraries implement circuit breakers, such as github.com/sony/gobreaker. Let's look at an example for integrating with an external service, perhaps for a Proof of Address database lookup.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/sony/gobreaker"
)

var cb *gobreaker.CircuitBreaker

func init() {
	st := gobreaker.Settings{
		Name:        "ExternalProofOfAddressService",
		MaxRequests: 3, // Allow 3 requests in half-open state
		Interval:    0, // Count errors forever
		Timeout:     5 * time.Second, // Open state duration
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			return counts.ConsecutiveFailures > 5 // Trip after 5 consecutive failures
		},
		OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
			log.Printf("Circuit Breaker '%s' changed from %s to %s", name, from, to)
		},
	}
	cb = gobreaker.NewCircuitBreaker(st)
}

func callProofOfAddressService() (string, error) {
	body, err := cb.Execute(func() (interface{}, error) {
		// Simulate calling an external service
		res, err := http.Get("http://localhost:8081/proof-of-address")
		if err != nil {
			return nil, err // Network errors trip the breaker
		}
		defer res.Body.Close()

		if res.StatusCode != http.StatusOK {
			return nil, fmt.Errorf("service responded with status: %d", res.StatusCode) // Non-200 status also trips
		}

		data, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return nil, err
		}
		return string(data), nil
	})
	
	if err != nil {
		// Handle circuit breaker open error or actual service error
		return "", fmt.Errorf("proof of address service call failed: %w", err)
	}
	return body.(string), nil
}

func main() {
	// Simulate a failing external service (run this in a separate terminal)
	// go func() {
	// 	http.HandleFunc("/proof-of-address", func(w http.ResponseWriter, r *http.Request) {
	// 		time.Sleep(100 * time.Millisecond)
	// 		// Simulate occasional failure
	// 		if time.Now().Second()%10 < 5 {
	// 			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	// 			return
	// 		}
	// 		fmt.Fprintf(w, "Address verified successfully!")
	// 	})
	// 	log.Fatal(http.ListenAndServe(":8081", nil))
	// }()

	http.HandleFunc("/check-address", func(w http.ResponseWriter, r *http.Request) {
		result, err := callProofOfAddressService()
		if err != nil {
			http.Error(w, err.Error(), http.StatusServiceUnavailable)
			return
		}
		fmt.Fprintf(w, result)
	})

	log.Println("Main server starting on port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

This circuit breaker ensures that if the external proof of address service starts failing, your application will quickly fail and return a StatusServiceUnavailable error instead of waiting for a timeout. This is vital for maintaining the responsiveness of your primary services, even when external dependencies falter. For services like 1:1 Face Match & Face Search, where real-time responses are often expected, circuit breakers can prevent a poor user experience caused by upstream latency.

Integrating and Monitoring Resilience Patterns

Implementing rate limiters and circuit breakers is only half the battle. Effective integration means applying these patterns at appropriate layers (e.g., API gateway, service mesh, or directly within your Go microservice). Comprehensive monitoring is crucial to observe when circuit breakers trip or rate limits are hit. Tools like Prometheus and Grafana can visualize these metrics, allowing you to fine-tune your configurations and respond quickly to incidents.

For identity verification workflows, especially those involving sensitive steps like NFC Verification (ePassport/eID), you need to ensure that these resilience mechanisms don't inadvertently block legitimate high-value transactions. Dynamic adjustments based on user behavior, transaction history, or risk scores (which Didit's platform helps generate) can refine these controls. A user attempting multiple Age Estimation requests might be legitimate, while a bot attempting to brute-force a login could be malicious.

How Didit Helps

While implementing robust resilience patterns in Go is a powerful capability for your internal services, Didit significantly simplifies the complexity of identity verification itself. Didit is the AI-native, developer-first identity platform designed for resilience and scale from the ground up. By leveraging Didit's services, you offload the heavy lifting of building and maintaining a highly available and fault-tolerant identity verification infrastructure.

  • Built-in Resilience: Didit's platform inherently incorporates advanced resilience mechanisms, including internal rate limiting, load balancing, and fault tolerance across its globally distributed infrastructure. This means your calls to Didit's APIs for ID Verification, Passive & Active Liveness, AML Screening & Monitoring, and other services are already protected.
  • Modular Architecture: Didit offers a modular architecture, allowing you to compose verification workflows precisely to your needs. Each module is designed for high availability, minimizing your exposure to single points of failure.
  • AI-Native Efficiency: As an AI-native platform, Didit optimizes processing for speed and accuracy, reducing the likelihood of internal bottlenecks that would necessitate complex client-side resilience logic.
  • Zero Setup Fees & Free Core KYC: You can start leveraging Didit's resilient platform immediately with Didit's free tier and benefit from its robust design without significant upfront investment.

By integrating with Didit, you can focus your Go development efforts on your core business logic, knowing that the identity verification components are handled by a world-class, resilient platform.

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.

Infrastructure for identity and fraud.

One API for KYC, KYB, Transaction Monitoring, and Wallet Screening. Integrate in 5 minutes.

Ask an AI to summarise this page
Go Rate Limiting & Circuit Breakers for Identity APIs.