net/http guide · Go SDK

Go rate limiting for net/http,per customer, across instances.

Wrap your mux once. Every request is checked against the caller’s key, charged against their credits, and held to their plan’s rate limit — no map of limiters to manage.

  • Keys issued per customer
  • Credits that refill
  • Limits per plan, not per process

Official SDKs with drop-in middleware for the stack you already run

  • Python
  • Node.js
  • Go
  • Rust
  • PHP
  • .NET
  • Java
API requests validated
100M+
Average key validation
<5ms
Average analytics ingest
<5ms
Check and log, end to end
<10ms

The usual net/http rate limiter counts requests. It doesn’t know your customers.

The standard library’s companion package gives you a token bucket. One limiter is global; per-customer limits mean a map of limiters, a mutex, and a way to evict old entries.

With x/time/ratetoday
// main.go — with x/time/ratevar limiter = rate.NewLimiter(rate.Limit(10), 20) // 10 req/s, bursts of 20

func limit(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !limiter.Allow() {
			http.Error(w, "too many requests", http.StatusTooManyRequests)
			return
		}
		next.ServeHTTP(w, r)
	})
}
  • One global bucket — per-customer limits need a map, a mutex, and eviction
  • Counts live in one process; a second replica doubles the limit
  • No API keys: issuing, hashing, scoping, and revoking them is still yours to build
  • No credit balance: a request can’t cost 5 on one route and 1 on another, or refill each month
  • One limit for everyone — no per-plan limits for Free, Pro, and Enterprise customers
  • No per-customer usage log to answer “why was I blocked?” or bill against
With ReqKey in net/httpone middleware
  • API keys issued per customer, prefixed, hashed, and revocable from the dashboard
  • A credit balance per customer — price each route, refill every hour, day, week, or month
  • Rate limits set per plan, shared by all of a customer’s keys, from 1 second to 24 hours
  • The same count on every worker, instance, and region — no Redis to run
  • Over the limit? A 429 with Retry-After, and no credits charged
  • Every request logged per customer — status, latency, endpoint — in under 5ms
Handlers read the verdict with reqkey.DecisionFromContext(r.Context()). See the code

net/http in three steps, one of them code.

ReqKey runs inside your API, not in front of it. Your server asks one question per request and gets an answer in under 5ms.

  1. 1

    Install the SDK

    go get github.com/Req-Key/reqkey-go@latest

  2. 2

    Set your project key

    Copy it from the dashboard into REQKEY_PROJECT_KEY. It stays on your server.

  3. 3

    Add the net/http middleware

    Every request is checked, charged, and logged before your handler runs.

main.gogo get github.com/Req-Key/reqkey-go@latest
package main
 
import (
"encoding/json"
"net/http"
"os"
 
"github.com/Req-Key/reqkey-go"
)
 
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /protected", func(w http.ResponseWriter, r *http.Request) {
decision, _ := reqkey.DecisionFromContext(r.Context())
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "request_id": decision.RequestID})
})
 
protect := reqkey.MustHTTPMiddleware(reqkey.MiddlewareOptions{
ProjectKey: os.Getenv("REQKEY_PROJECT_KEY"),
APIID: "api_payments",
Mode: reqkey.ModeBoth, // validate keys AND record analytics
KeyName: "X-Startup-Key", // where consumers send their key
ExcludePaths: []string{"/health", "/docs/*"},
})
 
_ = http.ListenAndServe(":8080", protect(mux))
}
Also in Go: Gin, Echo, Fiber, ChiFull Go reference

Every request gets one of these answers

  • 200Valid key, credits charged — your handler runs
  • 402Out of credits
  • 403Key disabled, or not allowed on this API
  • 429Over the rate limit — no credits charged

Where ReqKey sits

Your customer
Your APIReqKey, under 5ms
Your handler

Responses go straight back to your customer. ReqKey sees the key check and the log line, nothing else.

Credits

Charge each route what it costs you.

A lookup can cost 1 credit and a render 5, from the same balance. Excluded paths are never validated, charged, or recorded. In Go:

Per-route credits · Go
// Exact paths or trailing-* prefixes: never validated, charged, or recorded
ExcludePaths: []string{"/health", "/openapi.json", "/docs/*", "/cron/*"},
 
// Or decide per request with a resolver
ShouldProtect: func(ctx context.Context, request *reqkey.MiddlewareRequest) (bool, error) {
return strings.HasPrefix(request.Path, "/api/"), nil
},
 
// Charge different endpoints differently (non-negative integers)
CreditsResolver: func(ctx context.Context, request *reqkey.MiddlewareRequest) (int, error) {
if request.Method == http.MethodPost {
return 5, nil
}
return 1, nil
},

Rate limits

Set limits on plans, not in code.

Your net/http code never hard-codes a number. Each plan carries its credits, refill, and rate limit; moving a customer to Pro changes all three with no deploy.

PlanCreditsRate limit
Free1,000 / month5 req / s
Pro50,000 / month50 req / s
Scale1,000,000 / month500 req / s
429Over the limit, a call is answered with Retry-After and costs nothing — no credits and no quota.

Example plans. You name them and pick the numbers.

Notes for net/http teams hit in production.

Standard http.Handler

The middleware is func(http.Handler) http.Handler, so it composes with any router that speaks net/http — including Chi and gorilla/mux.

Concurrency-safe client

Create the middleware once at startup and share it across goroutines.

Go 1.22 routing

Method-and-path patterns like "POST /payments" work as usual; excludePaths keeps /health open.

net/http rate limiting: the questions teams ask.

Something else? Ask the team or read the docs.

  • For protecting one process from bursts, yes. For limits per paying customer across replicas — with keys, credits, and plans — you’d be building ReqKey yourself.

  • Yes. Both use the net/http contract, so pass the router to the middleware or register it with r.Use.

  • On their plan or on the customer (consumer) in ReqKey — a number of requests per window from 1 second to 24 hours, shared by all of that customer’s keys. Your net/http code never hard-codes a limit, so upgrading a customer is a dashboard change, not a deploy.

  • A check averages under 5ms. ReqKey runs inside your app as middleware, not as a gateway in front of it, so responses go straight back to your customer.

  • You choose: fail closed and answer 503, or fail open and let requests through. Invalid keys are denied either way, and a validation is never retried, so no one is charged twice.

Ship API keys in net/httpin five minutes.

Free for your first 5 million requests every month. No card, no gateway, no rewrite.